use hisab::Vec2;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PathStatus {
Found,
Partial,
NotFound,
Invalid,
Pending,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathRequest {
pub start: Vec2,
pub goal: Vec2,
}
impl PathRequest {
#[must_use]
pub fn new(start: Vec2, goal: Vec2) -> Self {
Self { start, goal }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathResult {
pub status: PathStatus,
pub waypoints: Vec<Vec2>,
pub length: f32,
}
impl PathResult {
#[must_use]
pub fn found(waypoints: Vec<Vec2>) -> Self {
let length = waypoints.windows(2).map(|w| (w[1] - w[0]).length()).sum();
Self {
status: PathStatus::Found,
waypoints,
length,
}
}
#[must_use]
pub fn partial(waypoints: Vec<Vec2>) -> Self {
let length = waypoints.windows(2).map(|w| (w[1] - w[0]).length()).sum();
Self {
status: PathStatus::Partial,
waypoints,
length,
}
}
#[must_use]
pub fn not_found() -> Self {
Self {
status: PathStatus::NotFound,
waypoints: Vec::new(),
length: 0.0,
}
}
#[must_use]
pub fn invalid() -> Self {
Self {
status: PathStatus::Invalid,
waypoints: Vec::new(),
length: 0.0,
}
}
#[must_use]
pub fn is_found(&self) -> bool {
self.status == PathStatus::Found
}
#[must_use]
pub fn is_partial(&self) -> bool {
self.status == PathStatus::Partial
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn path_result_found() {
let r = PathResult::found(vec![Vec2::ZERO, Vec2::new(3.0, 4.0)]);
assert!(r.is_found());
assert!((r.length - 5.0).abs() < 0.01);
}
#[test]
fn path_result_partial() {
let r = PathResult::partial(vec![Vec2::ZERO, Vec2::new(3.0, 4.0)]);
assert!(r.is_partial());
assert!(!r.is_found());
assert!((r.length - 5.0).abs() < 0.01);
}
#[test]
fn path_result_not_found() {
let r = PathResult::not_found();
assert!(!r.is_found());
assert_eq!(r.waypoints.len(), 0);
}
#[test]
fn path_result_invalid() {
let r = PathResult::invalid();
assert_eq!(r.status, PathStatus::Invalid);
}
#[test]
fn path_request_new() {
let req = PathRequest::new(Vec2::new(1.0, 2.0), Vec2::new(3.0, 4.0));
assert_eq!(req.start, Vec2::new(1.0, 2.0));
assert_eq!(req.goal, Vec2::new(3.0, 4.0));
}
#[test]
fn path_result_zero_length() {
let r = PathResult::found(vec![Vec2::new(5.0, 5.0)]);
assert!((r.length - 0.0).abs() < f32::EPSILON);
}
#[test]
fn path_result_serde_roundtrip() {
let r = PathResult::found(vec![Vec2::ZERO, Vec2::ONE]);
let json = serde_json::to_string(&r).unwrap();
let deserialized: PathResult = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.status, PathStatus::Found);
assert_eq!(deserialized.waypoints.len(), 2);
}
#[test]
fn path_result_multi_segment_length() {
let r = PathResult::found(vec![Vec2::ZERO, Vec2::new(3.0, 0.0), Vec2::new(3.0, 4.0)]);
assert!((r.length - 7.0).abs() < 0.01);
}
#[test]
fn path_result_empty_waypoints() {
let r = PathResult::found(vec![]);
assert!(r.is_found());
assert!((r.length - 0.0).abs() < f32::EPSILON);
}
#[test]
fn path_status_serde_roundtrip() {
let status = PathStatus::Pending;
let json = serde_json::to_string(&status).unwrap();
let deserialized: PathStatus = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, status);
}
}