use crate::core::scalar::ControlScalar;
use heapless::Vec as HVec;
use crate::trajectory::TrajectoryError;
struct Lcg {
state: u64,
}
impl Lcg {
#[inline]
fn new(seed: u64) -> Self {
Self { state: seed }
}
#[inline]
fn next_f64(&mut self) -> f64 {
self.state = self
.state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(self.state >> 11) as f64 / (1u64 << 53) as f64
}
}
pub struct RrtPlanner<S: ControlScalar, const N: usize, const MAX_NODES: usize> {
bounds_min: [S; N],
bounds_max: [S; N],
step_size: S,
rng: Lcg,
nodes: HVec<[S; N], MAX_NODES>,
parents: HVec<usize, MAX_NODES>,
costs: HVec<S, MAX_NODES>,
}
impl<S: ControlScalar, const N: usize, const MAX_NODES: usize> RrtPlanner<S, N, MAX_NODES> {
pub fn new(bounds_min: [S; N], bounds_max: [S; N], step_size: S, seed: u64) -> Self {
Self {
bounds_min,
bounds_max,
step_size,
rng: Lcg::new(seed),
nodes: HVec::new(),
parents: HVec::new(),
costs: HVec::new(),
}
}
pub fn reset(&mut self) {
self.nodes.clear();
self.parents.clear();
self.costs.clear();
}
fn dist(a: &[S; N], b: &[S; N]) -> S {
let mut sum = S::ZERO;
for (ai, bi) in a.iter().zip(b.iter()) {
let d = *ai - *bi;
sum += d * d;
}
sum.sqrt()
}
fn nearest(&self, q: &[S; N]) -> usize {
let mut best_idx = 0usize;
let mut best_dist = S::from_f64(f64::MAX);
for (i, node) in self.nodes.iter().enumerate() {
let d = Self::dist(node, q);
if d < best_dist {
best_dist = d;
best_idx = i;
}
}
best_idx
}
fn steer(&self, from: &[S; N], to: &[S; N]) -> [S; N] {
let d = Self::dist(from, to);
if d <= self.step_size || d == S::ZERO {
*to
} else {
let ratio = self.step_size / d;
let mut q = [S::ZERO; N];
for (qi, (fi, ti)) in q.iter_mut().zip(from.iter().zip(to.iter())) {
*qi = *fi + (*ti - *fi) * ratio;
}
q
}
}
fn sample(&mut self) -> [S; N] {
let mut q = [S::ZERO; N];
for (qi, (lo, hi)) in q
.iter_mut()
.zip(self.bounds_min.iter().zip(self.bounds_max.iter()))
{
let r = S::from_f64(self.rng.next_f64());
*qi = *lo + r * (*hi - *lo);
}
q
}
fn add_node(
&mut self,
config: [S; N],
parent: usize,
cost: S,
) -> Result<usize, TrajectoryError> {
let idx = self.nodes.len();
self.nodes
.push(config)
.map_err(|_| TrajectoryError::BufferFull)?;
self.parents
.push(parent)
.map_err(|_| TrajectoryError::BufferFull)?;
self.costs
.push(cost)
.map_err(|_| TrajectoryError::BufferFull)?;
Ok(idx)
}
fn rewire(&mut self, new_idx: usize) {
let radius = self.step_size * S::TWO;
let new_cfg = self.nodes[new_idx];
let new_cost = self.costs[new_idx];
for i in 0..self.nodes.len() {
if i == new_idx {
continue;
}
let d = Self::dist(&self.nodes[i], &new_cfg);
if d < radius {
let candidate_cost = new_cost + d;
if candidate_cost < self.costs[i] {
self.parents[i] = new_idx;
self.costs[i] = candidate_cost;
}
}
}
}
fn extract_path(&self, goal_idx: usize) -> Result<HVec<[S; N], MAX_NODES>, TrajectoryError> {
let mut indices: HVec<usize, MAX_NODES> = HVec::new();
let mut current = goal_idx;
loop {
indices
.push(current)
.map_err(|_| TrajectoryError::BufferFull)?;
if current == 0 {
break;
}
let parent = self.parents[current];
if parent == current {
break;
}
current = parent;
}
let mut path: HVec<[S; N], MAX_NODES> = HVec::new();
for &idx in indices.iter().rev() {
path.push(self.nodes[idx])
.map_err(|_| TrajectoryError::BufferFull)?;
}
Ok(path)
}
pub fn plan<F>(
&mut self,
start: [S; N],
goal: [S; N],
is_feasible: F,
max_iter: usize,
) -> Result<HVec<[S; N], MAX_NODES>, TrajectoryError>
where
F: Fn(&[S; N], &[S; N]) -> bool,
{
self.reset();
self.add_node(start, 0, S::ZERO)?;
let mut goal_idx: Option<usize> = None;
for iter in 0..max_iter {
let q_rand = if iter % 10 == 9 { goal } else { self.sample() };
let near_idx = self.nearest(&q_rand);
let near_cfg = self.nodes[near_idx];
let q_new = self.steer(&near_cfg, &q_rand);
if !is_feasible(&near_cfg, &q_new) {
continue;
}
let edge_cost = Self::dist(&near_cfg, &q_new);
let new_cost = self.costs[near_idx] + edge_cost;
let new_idx = match self.add_node(q_new, near_idx, new_cost) {
Ok(idx) => idx,
Err(_) => break, };
self.rewire(new_idx);
if Self::dist(&q_new, &goal) <= self.step_size {
if is_feasible(&q_new, &goal) {
let goal_cost = self.costs[new_idx] + Self::dist(&q_new, &goal);
let g_idx = match self.add_node(goal, new_idx, goal_cost) {
Ok(idx) => idx,
Err(_) => new_idx,
};
goal_idx = Some(g_idx);
break;
}
}
}
match goal_idx {
Some(idx) => self.extract_path(idx),
None => Err(TrajectoryError::NoPathFound),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plan_2d_no_obstacle() {
let mut planner: RrtPlanner<f64, 2, 4096> =
RrtPlanner::new([-1.0, -1.0], [1.0, 1.0], 0.15, 1234);
let result = planner.plan([0.0, 0.0], [0.8, 0.8], |_a, _b| true, 5000);
assert!(result.is_ok(), "Expected path, got {:?}", result.err());
let path = result.unwrap();
assert!(!path.is_empty());
let start = path[0];
assert!((start[0]).abs() < 1e-9);
assert!((start[1]).abs() < 1e-9);
let last = path[path.len() - 1];
assert!((last[0] - 0.8).abs() < 0.2, "last_x={}", last[0]);
assert!((last[1] - 0.8).abs() < 0.2, "last_y={}", last[1]);
}
#[test]
fn plan_2d_with_wall_obstacle() {
let is_feasible = |a: &[f64; 2], b: &[f64; 2]| -> bool {
let mx = (a[0] + b[0]) * 0.5;
let my = (a[1] + b[1]) * 0.5;
!(mx.abs() < 0.1 && my > 0.0)
};
let mut planner: RrtPlanner<f64, 2, 4096> =
RrtPlanner::new([-1.0, -1.0], [1.0, 1.0], 0.15, 9999);
let result = planner.plan([-0.5, -0.5], [0.5, 0.5], is_feasible, 8000);
let _ = result; }
#[test]
fn plan_3d_no_obstacle() {
let mut planner: RrtPlanner<f64, 3, 4096> = RrtPlanner::new([0.0; 3], [1.0; 3], 0.2, 42);
let result = planner.plan([0.0; 3], [0.9, 0.9, 0.9], |_a, _b| true, 8000);
assert!(result.is_ok(), "3-D planner failed: {:?}", result.err());
}
#[test]
fn path_has_minimum_length() {
let mut planner: RrtPlanner<f32, 2, 4096> =
RrtPlanner::new([-1.0f32, -1.0], [1.0, 1.0], 0.2, 7);
let path = planner
.plan([0.0f32, 0.0], [0.5, 0.5], |_a, _b| true, 3000)
.expect("should find path");
assert!(path.len() >= 2, "path.len()={}", path.len());
}
#[test]
fn rrt_costs_are_non_decreasing() {
let mut planner: RrtPlanner<f64, 2, 4096> =
RrtPlanner::new([-1.0, -1.0], [1.0, 1.0], 0.15, 555);
planner
.plan([0.0, 0.0], [0.7, 0.7], |_a, _b| true, 4000)
.expect("should find path");
for &c in planner.costs.iter() {
assert!(c >= 0.0, "negative cost encountered");
}
}
}