use crate::{
grid::{Cell, Grid, GridEditError},
point::Point,
replanning::{
InterpolatedGridReplanner, InterpolatedMovingGoalReplanner, InterpolatedPath,
InterpolatedQueryResult, InterpolatedSearchRequest, InterpolatedSearchResult,
InterpolatedTraversalCostModel, best_fallback_interpolated_path,
best_partial_interpolated_path, interpolated_segment_cost, query_interpolated_grid,
},
};
use condor_core::Point2;
const EPSILON: f64 = 1e-9;
pub struct FieldDStar {
grid: Option<Grid>,
request: Option<InterpolatedSearchRequest>,
cost_model: InterpolatedTraversalCostModel,
}
impl Default for FieldDStar {
fn default() -> Self {
Self::new()
}
}
impl FieldDStar {
#[must_use]
pub fn new() -> Self {
Self {
grid: None,
request: None,
cost_model: InterpolatedTraversalCostModel::CellLengthWeightedV0,
}
}
fn solve(&self, grid: &Grid, request: InterpolatedSearchRequest) -> InterpolatedSearchResult {
match query_interpolated_grid(grid, request) {
InterpolatedQueryResult::InvalidStart => {
Err(crate::InterpolatedSearchError::InvalidStart {
point: request.start,
})
}
InterpolatedQueryResult::InvalidGoal => {
Err(crate::InterpolatedSearchError::InvalidGoal {
point: request.goal,
})
}
InterpolatedQueryResult::NoPath { .. } => {
if let Some(path) = best_fallback_interpolated_path(grid, request, self.cost_model)?
{
return crate::replanning::interpolated_fallback(path, 0);
}
match best_partial_interpolated_path(grid, request, self.cost_model)? {
Some(path) => crate::replanning::interpolated_partial(path, 0),
None => crate::replanning::interpolated_not_found(0),
}
}
InterpolatedQueryResult::Connected { .. } => {
let Some((path, visited_nodes)) =
shortest_interpolated_path(grid, request, self.cost_model)
else {
return crate::replanning::interpolated_not_found(0);
};
crate::replanning::interpolated_found(path, visited_nodes)
}
}
}
}
impl InterpolatedGridReplanner for FieldDStar {
fn name(&self) -> &'static str {
"field-d-star"
}
fn initialize(
&mut self,
grid: &Grid,
request: InterpolatedSearchRequest,
) -> InterpolatedSearchResult {
self.grid = Some(grid.clone());
self.request = Some(request);
self.solve(grid, request)
}
fn update_cell(&mut self, point: Point, cell: Cell) {
if let Some(grid) = &mut self.grid {
let _ = grid.set_cell(point, cell);
}
}
fn update_cost(&mut self, point: Point, cost: usize) -> Result<(), GridEditError> {
if let Some(grid) = &mut self.grid {
return grid.set_traversal_cost(point, cost);
}
Ok(())
}
fn replan(&mut self) -> InterpolatedSearchResult {
match (&self.grid, self.request) {
(Some(grid), Some(request)) => self.solve(grid, request),
_ => Err(crate::InterpolatedSearchError::NotInitialized),
}
}
}
impl InterpolatedMovingGoalReplanner for FieldDStar {
fn update_goal(&mut self, goal: Point2) {
if let Some(request) = &mut self.request {
request.goal = goal;
}
}
}
fn shortest_interpolated_path(
grid: &Grid,
request: InterpolatedSearchRequest,
cost_model: InterpolatedTraversalCostModel,
) -> Option<(InterpolatedPath, usize)> {
let nodes = candidate_nodes(grid, request);
let goal_index = 1;
let mut distances = vec![f64::INFINITY; nodes.len()];
let mut parents = vec![None; nodes.len()];
let mut visited = vec![false; nodes.len()];
let mut visited_nodes = 0;
distances[0] = 0.0;
loop {
let current = next_unvisited_node(&distances, &visited)?;
if !distances[current].is_finite() {
break;
}
visited[current] = true;
visited_nodes += 1;
if current == goal_index {
break;
}
for neighbor in 0..nodes.len() {
if neighbor == current || visited[neighbor] {
continue;
}
let Some(step_cost) = candidate_step_cost(grid, &nodes, current, neighbor, cost_model)
else {
continue;
};
let candidate_cost = distances[current] + step_cost;
if candidate_cost + EPSILON < distances[neighbor] {
distances[neighbor] = candidate_cost;
parents[neighbor] = Some(current);
}
}
}
if !distances[goal_index].is_finite() {
return None;
}
let mut path_points = vec![nodes[goal_index]];
let mut cursor = goal_index;
while let Some(parent) = parents[cursor] {
cursor = parent;
path_points.push(nodes[cursor]);
}
path_points.reverse();
let path = match InterpolatedPath::from_points_on_grid(grid, path_points, cost_model) {
Ok(path) => path,
Err(_) => return None,
};
Some((path, visited_nodes))
}
fn candidate_step_cost(
grid: &Grid,
nodes: &[Point2],
current: usize,
neighbor: usize,
cost_model: InterpolatedTraversalCostModel,
) -> Option<f64> {
if !candidate_edge_allowed(nodes, current, neighbor) {
return None;
}
interpolated_segment_cost(grid, nodes[current], nodes[neighbor], cost_model)
}
fn candidate_edge_allowed(nodes: &[Point2], current: usize, neighbor: usize) -> bool {
let direct_start_to_goal = (current == 0 && neighbor == 1) || (current == 1 && neighbor == 0);
if direct_start_to_goal || axis_aligned(nodes[current], nodes[neighbor]) {
return true;
}
const START_INDEX: usize = 0;
const GOAL_INDEX: usize = 1;
edge_anchors_endpoint_to_its_cell(nodes, current, neighbor, START_INDEX)
|| edge_anchors_endpoint_to_its_cell(nodes, current, neighbor, GOAL_INDEX)
}
fn edge_anchors_endpoint_to_its_cell(
nodes: &[Point2],
current: usize,
neighbor: usize,
endpoint: usize,
) -> bool {
let other = if current == endpoint {
neighbor
} else if neighbor == endpoint {
current
} else {
return false;
};
same_point(nodes[other], containing_cell_center(nodes[endpoint]))
}
fn containing_cell_center(point: Point2) -> Point2 {
Point2::new(point.x.floor() + 0.5, point.y.floor() + 0.5)
}
fn next_unvisited_node(distances: &[f64], visited: &[bool]) -> Option<usize> {
let mut best_index = None;
let mut best_cost = f64::INFINITY;
for (index, cost) in distances.iter().copied().enumerate() {
if visited[index] || cost + EPSILON >= best_cost {
continue;
}
best_cost = cost;
best_index = Some(index);
}
best_index
}
fn candidate_nodes(grid: &Grid, request: InterpolatedSearchRequest) -> Vec<Point2> {
let mut nodes = vec![request.start, request.goal];
for index in 0..grid.cell_count() {
let point = grid.point_from_index(index);
if !grid.is_walkable(point) {
continue;
}
let center = cell_center(point);
if same_point(center, request.start) || same_point(center, request.goal) {
continue;
}
nodes.push(center);
}
nodes
}
fn same_point(left: Point2, right: Point2) -> bool {
(left.x - right.x).abs() <= EPSILON && (left.y - right.y).abs() <= EPSILON
}
fn axis_aligned(left: Point2, right: Point2) -> bool {
(left.x - right.x).abs() <= EPSILON || (left.y - right.y).abs() <= EPSILON
}
fn cell_center(point: Point) -> Point2 {
Point2::new(point.x as f64 + 0.5, point.y as f64 + 0.5)
}