#![deny(clippy::indexing_slicing)]
use crate::error::MotionError;
use crate::linear_algebra::Vector;
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndOfPath {
Stop,
Loop,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PathProjection<const DIMENSION: usize, T: Numeric = f64> {
point: Vector<DIMENSION, T>,
segment_index: usize,
arc_length: T,
distance: T,
}
impl<const DIMENSION: usize, T: Numeric> PathProjection<DIMENSION, T> {
#[inline]
pub fn point(&self) -> Vector<DIMENSION, T> {
self.point
}
#[inline]
#[must_use]
pub fn segment_index(&self) -> usize {
self.segment_index
}
#[inline]
#[must_use]
pub fn arc_length(&self) -> T {
self.arc_length
}
#[inline]
#[must_use]
pub fn distance(&self) -> T {
self.distance
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PolylinePath<const MAX_POINTS: usize, const DIMENSION: usize, T: Numeric = f64> {
points: [Vector<DIMENSION, T>; MAX_POINTS],
length: usize,
end_of_path: EndOfPath,
}
impl<const MAX_POINTS: usize, const DIMENSION: usize, T: Numeric> Default
for PolylinePath<MAX_POINTS, DIMENSION, T>
{
fn default() -> Self {
Self::new()
}
}
impl<const MAX_POINTS: usize, const DIMENSION: usize, T: Numeric>
PolylinePath<MAX_POINTS, DIMENSION, T>
{
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
points: [Vector::zeros(); MAX_POINTS],
length: 0,
end_of_path: EndOfPath::Stop,
}
}
pub fn try_from_points(points: &[Vector<DIMENSION, T>]) -> Result<Self, MotionError> {
if points.len() > MAX_POINTS {
return Err(MotionError::CapacityExceeded);
}
if points.iter().any(|point| !point.is_finite()) {
return Err(MotionError::NonFinite);
}
let mut path = Self::new();
for (slot, point) in path.points.iter_mut().zip(points.iter()) {
*slot = *point;
}
path.length = points.len();
Ok(path)
}
pub fn push(&mut self, point: Vector<DIMENSION, T>) -> Result<(), MotionError> {
if self.length == MAX_POINTS {
return Err(MotionError::CapacityExceeded);
}
if !point.is_finite() {
return Err(MotionError::NonFinite);
}
match self.points.get_mut(self.length) {
Some(slot) => {
*slot = point;
self.length += 1;
Ok(())
}
None => Err(MotionError::CapacityExceeded),
}
}
#[inline]
#[must_use]
pub fn with_end_of_path(mut self, mode: EndOfPath) -> Self {
self.end_of_path = mode;
self
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.length
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.length == 0
}
#[inline]
pub fn waypoints(&self) -> &[Vector<DIMENSION, T>] {
self.points.get(..self.length).unwrap_or(&[])
}
#[must_use]
pub fn total_arc_length(&self) -> T {
let mut total = T::ZERO;
for window in self.waypoints().windows(2) {
if let [a, b] = window {
total += (*b - *a).norm();
}
}
total
}
pub fn closest_point(
&self,
query: Vector<DIMENSION, T>,
) -> Result<PathProjection<DIMENSION, T>, MotionError> {
let waypoints = self.waypoints();
let first = match waypoints.first() {
Some(point) => *point,
None => return Err(MotionError::PathTooShort),
};
if self.length == 1 {
return Ok(PathProjection {
point: first,
segment_index: 0,
arc_length: T::ZERO,
distance: (query - first).norm(),
});
}
let mut best: Option<PathProjection<DIMENSION, T>> = None;
let mut arc_at_start = T::ZERO;
for (segment_index, window) in waypoints.windows(2).enumerate() {
let (a, b) = match window {
[a, b] => (*a, *b),
_ => continue,
};
let direction = b - a;
let denominator = direction.norm_squared();
let segment_length = direction.norm();
let (candidate, parameter) = if denominator == T::ZERO {
(a, T::ZERO)
} else {
let parameter = ((query - a).dot(direction) / denominator)
.max(T::ZERO)
.min(T::ONE);
(a + direction.scale(parameter), parameter)
};
let distance = (query - candidate).norm();
let improved = match &best {
Some(current) => distance < current.distance,
None => true,
};
if improved {
best = Some(PathProjection {
point: candidate,
segment_index,
arc_length: arc_at_start + segment_length * parameter,
distance,
});
}
arc_at_start += segment_length;
}
best.ok_or(MotionError::PathTooShort)
}
pub fn lookahead_point(
&self,
from_arc_length: T,
lookahead: T,
) -> Result<Vector<DIMENSION, T>, MotionError> {
let waypoints = self.waypoints();
let first = match waypoints.first() {
Some(point) => *point,
None => return Err(MotionError::PathTooShort),
};
if self.length == 1 {
return Ok(first);
}
let last = waypoints.last().copied().unwrap_or(first);
let total = self.total_arc_length();
let mut target = from_arc_length + lookahead;
match self.end_of_path {
EndOfPath::Stop => {
if target >= total {
return Ok(last);
}
}
EndOfPath::Loop => {
if total > T::ZERO {
target = target - total * (target / total).floor();
} else {
return Ok(first);
}
}
}
let mut arc_at_start = T::ZERO;
for window in waypoints.windows(2) {
let (a, b) = match window {
[a, b] => (*a, *b),
_ => continue,
};
let direction = b - a;
let segment_length = direction.norm();
if segment_length == T::ZERO {
continue;
}
if arc_at_start + segment_length >= target {
let parameter = (target - arc_at_start) / segment_length;
return Ok(a + direction.scale(parameter));
}
arc_at_start += segment_length;
}
Ok(last)
}
}