use crate::continuous::{PolygonPath, PolygonSearchResult};
use crate::polygonal::{Point2, PolygonScene};
use crate::shortest_path_map::{
ContinuousShortestPathMap, PolygonShortestPathMap, PolygonShortestPathMapBuildError,
PolygonShortestPathMapBuilder, PreparedContinuousShortestPathMap,
};
const EPSILON: f64 = 1e-9;
#[derive(Debug, Clone, Copy, Default)]
pub struct ContinuousMapPreparedGeometryKernelBuilder;
impl ContinuousMapPreparedGeometryKernelBuilder {
pub const CANDIDATE_ID: &str = "repeated-polygonal-fixed-source/prepared-geometry-kernel";
}
impl PolygonShortestPathMapBuilder for ContinuousMapPreparedGeometryKernelBuilder {
type Map = PreparedGeometryKernelMap;
fn name(&self) -> &'static str {
"prepared-geometry-kernel"
}
fn preprocess(
&self,
scene: &PolygonScene,
source: Point2,
) -> Result<Self::Map, PolygonShortestPathMapBuildError> {
let inner = ContinuousShortestPathMap.preprocess(scene, source)?;
Ok(PreparedGeometryKernelMap {
scene: scene.clone(),
inner,
})
}
}
#[derive(Debug, Clone)]
pub struct PreparedGeometryKernelMap {
scene: PolygonScene,
inner: PreparedContinuousShortestPathMap,
}
impl PolygonShortestPathMap for PreparedGeometryKernelMap {
fn name(&self) -> &'static str {
"prepared-geometry-kernel"
}
fn source(&self) -> Point2 {
self.inner.source()
}
fn query(&self, goal: Point2) -> PolygonSearchResult {
let source = self.inner.source();
if points_equal(source, goal) {
return crate::continuous::found(
PolygonPath::from_points(vec![source])
.expect("polygon path contains at least one point"),
1,
);
}
if self.scene.validate_goal(goal).is_err() {
return Err(crate::continuous::PolygonSearchError::InvalidGoal { point: goal });
}
if self.scene.segment_is_walkable(source, goal) {
let cost = source.distance_to(goal);
return crate::continuous::found(
PolygonPath::from_points_with_cost(vec![source, goal], cost)
.expect("polygon path contains at least one point"),
1,
);
}
self.inner.query(goal)
}
}
fn points_equal(left: Point2, right: Point2) -> bool {
(left.x - right.x).abs() <= EPSILON && (left.y - right.y).abs() <= EPSILON
}
#[cfg(test)]
mod tests {
use super::ContinuousMapPreparedGeometryKernelBuilder;
use crate::polygonal::{Point2, Polygon, PolygonScene, WorldBounds};
use crate::shortest_path_map::{
ContinuousShortestPathMap, PolygonShortestPathMap, PolygonShortestPathMapBuilder,
};
#[test]
fn query_parity_vs_continuous_shortest_path_map() {
let scene = PolygonScene {
world_bounds: WorldBounds::new(Point2::new(0.0, 0.0), Point2::new(12.0, 12.0)),
obstacles: vec![Polygon::new(vec![
Point2::new(4.0, 4.0),
Point2::new(6.0, 4.0),
Point2::new(6.0, 8.0),
Point2::new(4.0, 8.0),
])],
};
let source = Point2::new(1.0, 1.0);
let builder = ContinuousMapPreparedGeometryKernelBuilder;
let map = builder
.preprocess(&scene, source)
.expect("kernel map should preprocess");
let baseline = ContinuousShortestPathMap
.preprocess(&scene, source)
.expect("baseline map should preprocess");
assert_eq!(
ContinuousMapPreparedGeometryKernelBuilder::CANDIDATE_ID,
"repeated-polygonal-fixed-source/prepared-geometry-kernel"
);
assert_eq!(builder.name(), "prepared-geometry-kernel");
assert_eq!(map.name(), "prepared-geometry-kernel");
assert_eq!(map.source(), source);
for goal in [
Point2::new(5.0, 1.0),
Point2::new(10.0, 5.0),
Point2::new(10.0, 10.0),
Point2::new(1.0, 1.0),
] {
let candidate = map.query(goal).expect("valid goal");
let reference = baseline.query(goal).expect("valid goal");
assert_eq!(
candidate.is_found(),
reference.is_found(),
"found parity for goal {goal:?}"
);
match (candidate.cost(), reference.cost()) {
(Some(left), Some(right)) => {
assert!(
(left - right).abs() <= 1e-9,
"cost parity for goal {goal:?}: {left} vs {right}"
);
}
(None, None) => {}
other => panic!("cost shape mismatch for goal {goal:?}: {other:?}"),
}
}
}
#[test]
fn no_path_for_separator_goal() {
let scene = PolygonScene {
world_bounds: WorldBounds::new(Point2::new(0.0, 0.0), Point2::new(10.0, 10.0)),
obstacles: vec![Polygon::new(vec![
Point2::new(4.0, 0.0),
Point2::new(6.0, 0.0),
Point2::new(6.0, 10.0),
Point2::new(4.0, 10.0),
])],
};
let map = ContinuousMapPreparedGeometryKernelBuilder
.preprocess(&scene, Point2::new(2.0, 5.0))
.expect("source should preprocess");
let result = map
.query(Point2::new(8.0, 5.0))
.expect("valid goal endpoint");
assert!(!result.is_found());
assert!(result.path().is_none());
assert_eq!(result.cost(), None);
assert_eq!(
ContinuousMapPreparedGeometryKernelBuilder::CANDIDATE_ID,
"repeated-polygonal-fixed-source/prepared-geometry-kernel"
);
}
}