From 827cae1a4b7d5b2bc6f7a2b66335077c7b38e833 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Sun, 6 Oct 2024 17:17:48 -0500 Subject: [PATCH 1/7] Remove `Angle` and standardize direction types. --- src/core.rs | 377 +++++++++++++++++---------------------------- src/navigation.rs | 49 +++--- src/pathfinding.rs | 23 +-- 3 files changed, 176 insertions(+), 273 deletions(-) diff --git a/src/core.rs b/src/core.rs index 80ad04d..0f7cd5f 100644 --- a/src/core.rs +++ b/src/core.rs @@ -2,11 +2,14 @@ use std::{ cmp::{max, min}, f32::consts::PI, fmt::Display, - ops::Sub, sync::RwLock, }; -use bevy::{app::PluginGroupBuilder, math::FloatOrd, prelude::*}; +use bevy::{ + app::PluginGroupBuilder, + math::{CompassOctant, CompassQuadrant, FloatOrd}, + prelude::*, +}; use bevy_rapier2d::{ parry::query::{closest_points, distance, ClosestPoints}, prelude::*, @@ -16,175 +19,91 @@ use once_cell::sync::Lazy; use rand::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Reflect)] -pub enum Angle { - Degrees(f32), - Radians(f32), +fn relative_desc(rot: &Rot2) -> String { + let mode = RELATIVE_DIRECTION_MODE.read().unwrap(); + match rot.as_radians() { + v if v <= PI / 12. && v > -PI / 12. => "ahead", + v if v <= PI / 4. && v > PI / 12. => { + if *mode == RelativeDirectionMode::ClockFacing { + "11:00" + } else { + "ahead and left" + } + } + v if v <= 3. * PI / 8. && v > PI / 4. => { + if *mode == RelativeDirectionMode::ClockFacing { + "10:00" + } else { + "left and ahead" + } + } + v if v <= 5. * PI / 8. && v > 3. * PI / 8. => "left", + v if v <= 3. * PI / 4. && v > 5. * PI / 8. => { + if *mode == RelativeDirectionMode::ClockFacing { + "8:00" + } else { + "left and behind" + } + } + v if v <= 11. * PI / 12. && v > 3. * PI / 4. => { + if *mode == RelativeDirectionMode::ClockFacing { + "7:00" + } else { + "behind and left" + } + } + v if v <= PI && v > 11. * PI / 12. || v > -PI && v <= -11. * PI / 12. => "behind", + v if v <= -3. * PI / 4. && v > -11. * PI / 12. => { + if *mode == RelativeDirectionMode::ClockFacing { + "5:00" + } else { + "behind and right" + } + } + v if v <= -5. * PI / 8. && v > -3. * PI / 4. => { + if *mode == RelativeDirectionMode::ClockFacing { + "4:00" + } else { + "right and behind" + } + } + v if v <= -3. * PI / 8. && v > -5. * PI / 8. => "right", + v if v <= -PI / 4. && v > -3. * PI / 8. => { + if *mode == RelativeDirectionMode::ClockFacing { + "2:00" + } else { + "right and ahead" + } + } + v if v <= -PI / 12. && v > -PI / 4. => { + if *mode == RelativeDirectionMode::ClockFacing { + "1:00" + } else { + "ahead and right" + } + } + _ => "ahead", + } + .into() } -impl Default for Angle { - fn default() -> Self { - Self::Radians(0.) - } -} +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deref, DerefMut, Reflect)] +pub struct MovementDirection(CompassOctant); -impl Angle { - pub fn degrees(&self) -> f32 { - use Angle::*; - let mut degrees: f32 = match self { - Degrees(v) => *v, - Radians(v) => v.to_degrees(), - }; - while degrees < 0. { - degrees += 360.; - } - while degrees >= 360. { - degrees %= 360.; - } - degrees - } - - pub fn degrees_u32(&self) -> u32 { - self.degrees() as u32 - } - - pub fn radians(&self) -> f32 { - use Angle::*; - match self { - Degrees(v) => v.to_radians(), - Radians(v) => *v, - } - } - - fn relative_desc(&self) -> String { - let mode = RELATIVE_DIRECTION_MODE.read().unwrap(); - match self.degrees() { - v if v <= 15. => "ahead", - v if v <= 45. => { - if *mode == RelativeDirectionMode::ClockFacing { - "11:00" - } else { - "ahead and left" - } - } - v if v <= 75. => { - if *mode == RelativeDirectionMode::ClockFacing { - "10:00" - } else { - "left and ahead" - } - } - v if v <= 105. => "left", - v if v <= 135. => { - if *mode == RelativeDirectionMode::ClockFacing { - "8:00" - } else { - "left and behind" - } - } - v if v <= 165. => { - if *mode == RelativeDirectionMode::ClockFacing { - "7:00" - } else { - "behind and left" - } - } - v if v <= 195. => "behind", - v if v <= 225. => { - if *mode == RelativeDirectionMode::ClockFacing { - "5:00" - } else { - "behind and right" - } - } - v if v <= 255. => { - if *mode == RelativeDirectionMode::ClockFacing { - "4:00" - } else { - "right and behind" - } - } - v if v <= 285. => "right", - v if v <= 315. => { - if *mode == RelativeDirectionMode::ClockFacing { - "2:00" - } else { - "right and ahead" - } - } - v if v <= 345. => { - if *mode == RelativeDirectionMode::ClockFacing { - "1:00" - } else { - "ahead and right" - } - } - _ => "ahead", - } - .into() - } -} - -impl Sub for Angle { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - match self { - Angle::Degrees(v1) => match rhs { - Angle::Degrees(v2) => Angle::Degrees(v1 - v2), - Angle::Radians(v2) => Angle::Degrees(v1 - v2.to_degrees()), - }, - Angle::Radians(v1) => match rhs { - Angle::Degrees(v2) => Angle::Radians(v1 - v2.to_radians()), - Angle::Radians(v2) => Angle::Radians(v1 - v2), - }, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Reflect)] -pub enum MovementDirection { - North, - NorthNortheast, - Northeast, - EastNortheast, - East, - EastSoutheast, - Southeast, - SouthSoutheast, - South, - SouthSouthwest, - Southwest, - WestSouthwest, - West, - WestNorthwest, - Northwest, - NorthNorthwest, -} - -impl From for MovementDirection { - fn from(angle: Angle) -> Self { - let heading = angle.degrees(); - use MovementDirection::*; - match heading { - h if h < 11.5 => East, - h if h < 34.0 => EastNortheast, - h if h < 56.5 => Northeast, - h if h < 79.0 => NorthNortheast, - h if h < 101.5 => North, - h if h < 124.0 => NorthNorthwest, - h if h < 146.5 => Northwest, - h if h < 169.0 => WestNorthwest, - h if h < 191.5 => West, - h if h < 214.0 => WestSouthwest, - h if h < 236.5 => Southwest, - h if h < 259.0 => SouthSouthwest, - h if h < 281.5 => South, - h if h < 304.0 => SouthSoutheast, - h if h < 326.5 => Southeast, - h if h <= 349.0 => EastSoutheast, - _ => East, - } +impl From for MovementDirection { + fn from(rot: Rot2) -> Self { + use CompassOctant::*; + MovementDirection(match rot.as_radians() { + h if h > -PI / 8. && h <= PI / 8. => East, + h if h > PI / 8. && h <= 3. * PI / 8. => NorthEast, + h if h > 3. * PI / 8. && h <= 5. * PI / 8. => North, + h if h > 5. * PI / 8. && h <= 7. * PI / 8. => NorthWest, + h if h > 7. * PI / 8. || h <= -7. * PI / 8. => East, + h if h > -7. * PI / 8. && h <= -5. * PI / 8. => SouthEast, + h if h > -5. * PI / 8. && h <= -3. * PI / 8. => South, + h if h > -3. * PI / 8. && h <= -PI / 8. => SouthWest, + _ => West, + }) } } @@ -192,25 +111,18 @@ impl From for MovementDirection { #[allow(clippy::from_over_into)] impl Into for MovementDirection { fn into(self) -> String { - use MovementDirection::*; - match self { - North => "north".to_string(), - NorthNortheast => "north northeast".to_string(), - Northeast => "northeast".to_string(), - EastNortheast => "east northeast".to_string(), - East => "east".to_string(), - EastSoutheast => "east southeast".to_string(), - Southeast => "southeast".to_string(), - SouthSoutheast => "south southeast".to_string(), - South => "south".to_string(), - SouthSouthwest => "south southwest".to_string(), - Southwest => "southwest".to_string(), - WestSouthwest => "west southwest".to_string(), - West => "west".to_string(), - WestNorthwest => "west northwest".to_string(), - Northwest => "northwest".to_string(), - NorthNorthwest => "north northwest".to_string(), + use CompassOctant::*; + match self.0 { + North => "north", + NorthEast => "northeast", + East => "east", + SouthEast => "southeast", + South => "south", + SouthWest => "southwest", + West => "west", + NorthWest => "northwest", } + .into() } } @@ -221,38 +133,36 @@ impl Display for MovementDirection { } } -#[derive(Component, Clone, Copy, Default, Debug, Eq, PartialEq, Reflect)] +#[derive(Component, Clone, Copy, Debug, Eq, PartialEq, Deref, DerefMut, Reflect)] #[reflect(Component)] -pub enum CardinalDirection { - #[default] - North, - East, - South, - West, -} +pub struct CardinalDirection(pub CompassQuadrant); -impl From for CardinalDirection { - fn from(angle: Angle) -> Self { - let heading = angle.degrees(); - use CardinalDirection::*; - match heading { - h if h <= 45. => East, - h if h <= 135. => North, - h if h <= 225. => West, - h if h <= 315. => South, - _ => East, - } +impl Default for CardinalDirection { + fn default() -> Self { + Self(CompassQuadrant::East) } } -impl From<&CardinalDirection> for Angle { +impl From for CardinalDirection { + fn from(rot: Rot2) -> Self { + use CompassQuadrant::*; + CardinalDirection(match rot.as_radians() { + h if h > -PI / 4. && h <= PI / 4. => East, + h if h > PI / 4. && h <= 3. * PI / 4. => North, + h if h > 3. * PI / 4. || h <= -3. * PI / 4. => West, + _ => South, + }) + } +} + +impl From<&CardinalDirection> for Rot2 { fn from(direction: &CardinalDirection) -> Self { - use CardinalDirection::*; - match direction { - North => Angle::Radians(PI / 2.), - East => Angle::Radians(0.), - South => Angle::Radians(PI * 1.5), - West => Angle::Radians(PI), + use CompassQuadrant::*; + match direction.0 { + North => Rot2::radians(PI / 2.), + East => Rot2::radians(0.), + South => Rot2::radians(-PI / 2.), + West => Rot2::radians(PI), } } } @@ -261,8 +171,8 @@ impl From<&CardinalDirection> for Angle { #[allow(clippy::from_over_into)] impl Into for CardinalDirection { fn into(self) -> String { - use CardinalDirection::*; - match self { + use CompassQuadrant::*; + match self.0 { North => "north".to_string(), East => "east".to_string(), South => "south".to_string(), @@ -342,24 +252,26 @@ pub trait PointLike { self.distance_squared(other).sqrt() } - fn bearing(&self, other: &dyn PointLike) -> Angle { + fn bearing(&self, other: &dyn PointLike) -> Rot2 { let y = other.y() - self.y(); let x = other.x() - self.x(); - Angle::Radians(y.atan2(x)) + Rot2::radians(y.atan2(x)) } fn direction(&self, other: &dyn PointLike) -> MovementDirection { self.bearing(other).into() } - fn direction_and_distance(&self, other: &dyn PointLike, yaw: Option) -> String { + fn direction_and_distance(&self, other: &dyn PointLike, yaw: Option) -> String { let mut tokens: Vec = vec![]; let distance = self.distance(other).round() as i32; if distance > 0 { let tile_or_tiles = if distance == 1 { "tile" } else { "tiles" }; let direction: String = if let Some(yaw) = yaw { - let bearing = self.bearing(other); - (bearing - yaw).relative_desc() + let yaw = yaw.as_radians(); + let bearing = self.bearing(other).as_radians(); + let rot = Rot2::radians(bearing - yaw); + relative_desc(&rot) } else { self.direction(other).into() }; @@ -481,18 +393,18 @@ impl From<&dyn PointLike> for (i32, i32) { } pub trait TransformExt { - fn yaw(&self) -> Angle; + fn yaw(&self) -> Rot2; } impl TransformExt for Transform { - fn yaw(&self) -> Angle { + fn yaw(&self) -> Rot2 { let forward = self.right(); - Angle::Radians(forward.y.atan2(forward.x)) + Rot2::radians(forward.y.atan2(forward.x)) } } pub trait GlobalTransformExt { - fn yaw(&self) -> Angle; + fn yaw(&self) -> Rot2; fn closest_points( &self, @@ -510,9 +422,9 @@ pub trait GlobalTransformExt { } impl GlobalTransformExt for GlobalTransform { - fn yaw(&self) -> Angle { + fn yaw(&self) -> Rot2 { let forward = self.right(); - Angle::Radians(forward.y.atan2(forward.x)) + Rot2::radians(forward.y.atan2(forward.x)) } fn closest_points( @@ -524,11 +436,11 @@ impl GlobalTransformExt for GlobalTransform { let scale = PHYSICS_SCALE.read().unwrap(); let pos1 = Isometry::new( (self.translation() / *scale).xy().into(), - self.yaw().radians(), + self.yaw().as_radians(), ); let pos2 = Isometry::new( (other.translation() / *scale).xy().into(), - other.yaw().radians(), + other.yaw().as_radians(), ); closest_points(&pos1, &*collider.raw, &pos2, &*other_collider.raw, f32::MAX).unwrap() } @@ -542,11 +454,11 @@ impl GlobalTransformExt for GlobalTransform { let scale = PHYSICS_SCALE.read().unwrap(); let pos1 = Isometry::new( (self.translation() / *scale).xy().into(), - self.yaw().radians(), + self.yaw().as_radians(), ); let pos2 = Isometry::new( (other.translation() / *scale).xy().into(), - other.yaw().radians(), + other.yaw().as_radians(), ); let closest = self.closest_points(collider, other, other_collider); let distance = distance(&pos1, &*collider.raw, &pos2, &*other_collider.raw).unwrap() as u32; @@ -555,9 +467,10 @@ impl GlobalTransformExt for GlobalTransform { if let ClosestPoints::WithinMargin(p1, p2) = closest { let p1 = (p1.x, p1.y); let p2 = (p2.x, p2.y); - let bearing = p1.bearing(&p2); - let yaw = self.yaw(); - let direction = (bearing - yaw).relative_desc(); + let bearing = p1.bearing(&p2).as_radians(); + let yaw = self.yaw().as_radians(); + let rot = Rot2::radians(bearing - yaw); + let direction = relative_desc(&rot); format!("{direction} {distance} {tile_or_tiles}") } else { format!("{} {}", distance, tile_or_tiles) diff --git a/src/navigation.rs b/src/navigation.rs index 7f3c451..8f6d084 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -1,12 +1,12 @@ use std::{collections::HashMap, error::Error, f32::consts::PI, fmt::Debug, hash::Hash}; -use bevy::prelude::*; +use bevy::{math::CompassQuadrant, prelude::*}; use bevy_rapier2d::prelude::*; use bevy_tts::Tts; use leafwing_input_manager::prelude::*; use crate::{ - core::{Angle, Area, CardinalDirection, GlobalTransformExt, Player, TransformExt}, + core::{Area, CardinalDirection, GlobalTransformExt, Player, TransformExt}, error::error_handler, log::Log, utils::target_and_other, @@ -74,7 +74,7 @@ impl Default for StrafeMovementFactor { #[derive(Component, Clone, Copy, Default, Debug, Deref, DerefMut, Reflect)] #[reflect(Component)] -pub struct RotationSpeed(pub Angle); +pub struct RotationSpeed(pub Rot2); #[derive(Deref, DerefMut)] struct SnapTimer(Timer); @@ -116,26 +116,28 @@ fn snap( continue; } else if actions.just_pressed(&NavigationAction::SnapLeft) { snap_timers.insert(entity, SnapTimer::default()); - transform.rotation = Quat::from_rotation_z(match direction { - CardinalDirection::North => PI, - CardinalDirection::East => PI / 2., - CardinalDirection::South => 0., - CardinalDirection::West => -PI / 2., + transform.rotation = Quat::from_rotation_z(match direction.0 { + CompassQuadrant::North => PI, + CompassQuadrant::East => PI / 2., + CompassQuadrant::South => 0., + CompassQuadrant::West => -PI / 2., }); } else if actions.just_pressed(&NavigationAction::SnapRight) { snap_timers.insert(entity, SnapTimer::default()); - transform.rotation = Quat::from_rotation_z(match direction { - CardinalDirection::North => 0., - CardinalDirection::East => -PI / 2., - CardinalDirection::South => PI, - CardinalDirection::West => PI / 2., + transform.rotation = Quat::from_rotation_z(match direction.0 { + CompassQuadrant::North => 0., + CompassQuadrant::East => -PI / 2., + CompassQuadrant::South => PI, + CompassQuadrant::West => PI / 2., }); } else if actions.just_pressed(&NavigationAction::SnapReverse) { snap_timers.insert(entity, SnapTimer::default()); transform.rotate(Quat::from_rotation_z(PI)); } else if actions.just_pressed(&NavigationAction::SnapCardinal) { - let yaw: Angle = direction.into(); - let yaw = yaw.radians(); + println!("Direction: {direction:?}"); + let yaw: Rot2 = direction.into(); + let yaw = yaw.as_radians(); + println!("Yaw: {yaw}"); transform.rotation = Quat::from_rotation_z(yaw); tts.speak(direction.to_string(), true)?; } @@ -220,7 +222,7 @@ fn controls( if rapier_context .cast_shape( transform.translation.truncate(), - transform.yaw().radians(), + transform.yaw().as_radians(), pair, collider, ShapeCastOptions { @@ -240,7 +242,7 @@ fn controls( if !snap_timers.contains_key(&entity) { if let Some(rotation_speed) = rotation_speed { let delta = - -rotation_speed.radians() * actions.clamped_value(&NavigationAction::Rotate); + rotation_speed.as_radians() * actions.clamped_value(&NavigationAction::Rotate); actions.set_value(&NavigationAction::SetAngularVelocity, delta); } } @@ -288,16 +290,11 @@ fn remove_direction( fn speak_direction( mut tts: ResMut, - player: Query< - (&CardinalDirection, Ref), - (With, Changed), - >, + player: Query<&CardinalDirection, (With, Changed)>, ) -> Result<(), Box> { - if let Ok((direction, change)) = player.get_single() { - if !change.is_added() { - let direction: String = (*direction).into(); - tts.speak(direction, true)?; - } + if let Ok(direction) = player.get_single() { + let direction: String = (*direction).into(); + tts.speak(direction, true)?; } Ok(()) } diff --git a/src/pathfinding.rs b/src/pathfinding.rs index dabd6d6..6ea1304 100644 --- a/src/pathfinding.rs +++ b/src/pathfinding.rs @@ -175,11 +175,9 @@ fn calculate_path( >, ) { for (entity, handle, destination, coordinates, shape, cost_map) in &query { - trace -!("{entity}: destination: {destination:?}"); + trace!("{entity}: destination: {destination:?}"); if coordinates.i32() == **destination { - trace -!("{entity}: remove1"); + trace!("{entity}: remove1"); commands .entity(entity) .remove::() @@ -229,8 +227,7 @@ fn calculate_path( shape_clone, ) }); - trace -!("{entity}: remove2"); + trace!("{entity}: remove2"); commands .entity(entity) .insert(Calculating(task)) @@ -255,8 +252,7 @@ fn poll_tasks( trace!("{entity:?}: Path: {path:?}"); commands.entity(entity).insert(path); } else { - trace -!( + trace!( "{entity:?}: path: no path from {:?} to {:?}", transform.translation.truncate().i32(), **destination @@ -349,7 +345,7 @@ fn negotiate_path( if rapier_context .cast_shape( start, - transform.yaw().radians(), + transform.yaw().as_radians(), direction, collider, ShapeCastOptions { @@ -362,8 +358,7 @@ fn negotiate_path( ) .is_some() { - trace -!("{entity:?} is stuck"); + trace!("{entity:?} is stuck"); // TODO: Remove when we have an actual character controller. next.x = next.x.trunc(); next.y = next.y.trunc(); @@ -376,8 +371,7 @@ fn negotiate_path( transform.rotation = Quat::from_rotation_z(angle); } } else { - trace -!("{entity:?}: empty path, cleaning"); + trace!("{entity:?}: empty path, cleaning"); if let Some(mut actions) = actions { trace!("{entity:?}: Disabling pathfind because at destination"); actions.reset_all(); @@ -421,8 +415,7 @@ fn actions( *current_dest = dest; } } else { - trace -!("{entity:?}: Adding destination, zeroing velocity"); + trace!("{entity:?}: Adding destination, zeroing velocity"); navigation_action.set_axis_pair(&NavigationAction::SetLinearVelocity, Vec2::ZERO); commands.entity(entity).insert(dest); } From 44c40f64732581086fd7f3369182a0da91450a02 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Sun, 1 Dec 2024 14:12:32 -0600 Subject: [PATCH 2/7] Remove unnecessary sets/speech from navigation controls. --- src/navigation.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/navigation.rs b/src/navigation.rs index 8f6d084..c4167bb 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -99,7 +99,6 @@ impl Default for Speed { } fn snap( - mut tts: ResMut, mut snap_timers: ResMut, mut query: Query< ( @@ -110,7 +109,7 @@ fn snap( ), With, >, -) -> Result<(), Box> { +) { for (entity, actions, mut transform, direction) in &mut query { if snap_timers.contains_key(&entity) { continue; @@ -138,11 +137,12 @@ fn snap( let yaw: Rot2 = direction.into(); let yaw = yaw.as_radians(); println!("Yaw: {yaw}"); - transform.rotation = Quat::from_rotation_z(yaw); - tts.speak(direction.to_string(), true)?; + let rotation = Quat::from_rotation_z(yaw); + if transform.rotation != rotation { + transform.rotation = rotation; + } } } - Ok(()) } fn tick_snap_timers(time: Res