pub(crate) mod geometry;
use std::{error::Error, fmt};
use condor_core::{BudgetExhausted, Point2, SearchBudget};
use crate::{
grid::Grid,
search::{SearchOutcome, SearchPathCost, SearchVisitStats},
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AnyAngleSearchRequest {
pub start: Point2,
pub goal: Point2,
pub budget: SearchBudget,
}
impl AnyAngleSearchRequest {
#[must_use]
pub fn new(start: Point2, goal: Point2) -> Self {
Self {
start,
goal,
budget: SearchBudget::UNLIMITED,
}
}
#[must_use]
pub const fn with_budget(mut self, budget: SearchBudget) -> Self {
self.budget = budget;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnyAnglePath {
points: Vec<Point2>,
cost: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum AnyAnglePathBuildError {
#[error("any-angle paths must contain at least one point")]
Empty,
}
impl AnyAnglePath {
pub fn from_points(points: Vec<Point2>) -> Result<Self, AnyAnglePathBuildError> {
if points.is_empty() {
return Err(AnyAnglePathBuildError::Empty);
}
let cost = points
.windows(2)
.map(|pair| pair[0].distance_to(pair[1]))
.sum();
Ok(Self { points, cost })
}
pub fn from_points_with_cost(
points: Vec<Point2>,
cost: f64,
) -> Result<Self, AnyAnglePathBuildError> {
if points.is_empty() {
return Err(AnyAnglePathBuildError::Empty);
}
Ok(Self { points, cost })
}
#[must_use]
pub fn points(&self) -> &[Point2] {
&self.points
}
#[must_use]
pub fn len(&self) -> usize {
self.points.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
#[must_use]
pub fn start(&self) -> Point2 {
self.points[0]
}
#[must_use]
pub fn goal(&self) -> Point2 {
self.points[self.points.len() - 1]
}
#[must_use]
pub const fn cost(&self) -> f64 {
self.cost
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct AnyAngleSearchStats {
pub visited_nodes: usize,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnyAngleSearchError {
InvalidStart { point: Point2 },
InvalidGoal { point: Point2 },
BudgetExhausted(BudgetExhausted),
}
impl fmt::Display for AnyAngleSearchError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidStart { point } => write!(formatter, "invalid any-angle start: {point:?}"),
Self::InvalidGoal { point } => write!(formatter, "invalid any-angle goal: {point:?}"),
Self::BudgetExhausted(reason) => write!(formatter, "{reason}"),
}
}
}
impl Error for AnyAngleSearchError {}
pub type AnyAngleSearchResult =
Result<SearchOutcome<AnyAnglePath, AnyAngleSearchStats>, AnyAngleSearchError>;
pub(crate) const fn found(path: AnyAnglePath, visited_nodes: usize) -> AnyAngleSearchResult {
Ok(SearchOutcome::found(
path,
AnyAngleSearchStats { visited_nodes },
))
}
pub(crate) const fn not_found(visited_nodes: usize) -> AnyAngleSearchResult {
Ok(SearchOutcome::no_path(AnyAngleSearchStats {
visited_nodes,
}))
}
impl SearchPathCost for AnyAnglePath {
type Cost = f64;
fn path_cost(&self) -> Self::Cost {
self.cost()
}
}
impl SearchVisitStats for AnyAngleSearchStats {
fn visited_nodes(&self) -> usize {
self.visited_nodes
}
}
pub trait AnyAnglePathfinder {
fn name(&self) -> &'static str;
fn search(&self, grid: &Grid, request: AnyAngleSearchRequest) -> AnyAngleSearchResult;
}
#[doc(hidden)]
pub const fn budget_error(reason: BudgetExhausted) -> AnyAngleSearchError {
AnyAngleSearchError::BudgetExhausted(reason)
}
#[must_use]
pub fn has_line_of_sight(grid: &Grid, start: Point2, end: Point2) -> bool {
geometry::sampling_segment_is_legal(grid, start, end)
}
#[doc(hidden)]
pub mod exact_oracle_v0 {
pub use crate::algorithms::any_angle_visibility_graph::{
AnyAngleOracleDiagnostics, AnyAngleSamplingReferenceOracle, AnyAngleVisibilityGraphOracle,
};
pub use crate::any_angle::geometry::{
approximately_equal, canonicalize_grid_vertex, extract_boundary_edges, is_endpoint_valid,
recompute_path_cost, retained_visibility_vertices, sampling_segment_is_legal,
segment_is_legal, validate_path, validate_sampling_path,
};
}