#![deny(clippy::indexing_slicing)]
#[cfg(feature = "alloc")]
use alloc::{vec, vec::Vec};
#[cfg(feature = "alloc")]
use crate::error::MappingError;
use crate::scalar::{Numeric, Primal};
pub trait OccupancyMap<T: Numeric + Primal = f64> {
#[must_use]
fn columns(&self) -> usize;
#[must_use]
fn rows(&self) -> usize;
#[must_use]
fn resolution(&self) -> T;
#[must_use]
fn origin(&self) -> [T; 2];
#[must_use]
fn is_occupied(&self, row: usize, column: usize) -> bool;
fn cell_of(&self, point: [T; 2]) -> Option<(usize, usize)> {
let origin = self.origin();
let column = ((point[0] - origin[0]) / self.resolution())
.floor()
.to_f64();
let row = ((point[1] - origin[1]) / self.resolution())
.floor()
.to_f64();
if column < 0.0 || row < 0.0 {
return None;
}
let (row, column) = (row as usize, column as usize);
(column < self.columns() && row < self.rows()).then_some((row, column))
}
fn cast_ray(&self, start_position: [T; 2], bearing: T, maximum_range: T) -> Option<T> {
if self.columns() == 0 || self.rows() == 0 {
return None;
}
let direction = [bearing.cos(), bearing.sin()];
let entry = entry_distance(self, start_position, direction)?;
if entry > maximum_range {
return None;
}
let point = [
start_position[0] + entry * direction[0],
start_position[1] + entry * direction[1],
];
let lowest_corner = self.origin();
let resolution = self.resolution();
let mut column = clamp_index((point[0] - lowest_corner[0]) / resolution, self.columns());
let mut row = clamp_index((point[1] - lowest_corner[1]) / resolution, self.rows());
let step_column = axis_step(direction[0]);
let step_row = axis_step(direction[1]);
let stride_column = if direction[0] == T::ZERO {
T::INFINITY
} else {
resolution / direction[0].abs()
};
let stride_row = if direction[1] == T::ZERO {
T::INFINITY
} else {
resolution / direction[1].abs()
};
let mut next_column = boundary_distance(
start_position[0],
direction[0],
column,
step_column,
lowest_corner[0],
resolution,
);
let mut next_row = boundary_distance(
start_position[1],
direction[1],
row,
step_row,
lowest_corner[1],
resolution,
);
let mut entered = entry;
loop {
if entered > maximum_range {
return None;
}
if column < 0 || row < 0 {
return None;
}
let (column_index, row_index) = (column as usize, row as usize);
if column_index >= self.columns() || row_index >= self.rows() {
return None;
}
if self.is_occupied(row_index, column_index) {
return Some(entered);
}
if next_column < next_row {
column += step_column;
entered = next_column;
next_column += stride_column;
} else {
row += step_row;
entered = next_row;
next_row += stride_row;
}
}
}
}
pub trait MutableOccupancyMap<T: Numeric + Primal = f64>: OccupancyMap<T> {
fn set_cell(&mut self, row: usize, column: usize, occupied: bool);
fn clear(&mut self);
fn occupy_point(&mut self, point: [T; 2]) {
if let Some((row, column)) = self.cell_of(point) {
self.set_cell(row, column, true);
}
}
fn occupy_polyline(&mut self, polyline: &[[T; 2]], closed: bool) {
let step = T::from_f64(0.4) * self.resolution();
let count = polyline.len();
let edges = if closed {
count
} else {
count.saturating_sub(1)
};
for index in 0..edges {
let (Some(&start), Some(&end)) = (
polyline.get(index),
polyline.get((index + 1) % count.max(1)),
) else {
continue;
};
let length = (end[0] - start[0]).hypot(end[1] - start[1]);
let samples = (length / step).ceil().max(T::ONE).to_f64() as usize;
for sample in 0..=samples {
let fraction = T::from_usize(sample) / T::from_usize(samples);
self.occupy_point([
start[0] + fraction * (end[0] - start[0]),
start[1] + fraction * (end[1] - start[1]),
]);
}
}
}
fn occupy_circle(&mut self, center: [T; 2], radius: T) {
let step = (T::from_f64(0.4) * self.resolution() / radius).max(T::from_f64(1e-3));
let mut angle = T::ZERO;
while angle < T::TWO_PI {
self.occupy_point([
center[0] + radius * angle.cos(),
center[1] + radius * angle.sin(),
]);
angle += step;
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Debug, Clone, PartialEq)]
pub struct DynamicOccupancyGrid<T: Numeric + Primal = f64> {
columns: usize,
rows: usize,
resolution: T,
origin: [T; 2],
cells: Vec<bool>,
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<T: Numeric + Primal> DynamicOccupancyGrid<T> {
pub fn try_new(
columns: usize,
rows: usize,
resolution: T,
origin: [T; 2],
) -> Result<Self, MappingError> {
if columns == 0 || rows == 0 {
return Err(MappingError::EmptyGrid);
}
let count = columns
.checked_mul(rows)
.ok_or(MappingError::GridTooLarge)?;
if !resolution.is_finite() || !origin[0].is_finite() || !origin[1].is_finite() {
return Err(MappingError::NonFinite);
}
if resolution <= T::ZERO {
return Err(MappingError::NonPositiveResolution);
}
Ok(DynamicOccupancyGrid {
columns,
rows,
resolution,
origin,
cells: vec![false; count],
})
}
fn index_of(&self, row: usize, column: usize) -> Option<usize> {
(row < self.rows && column < self.columns).then(|| row * self.columns + column)
}
}
#[cfg(feature = "alloc")]
impl<T: Numeric + Primal> OccupancyMap<T> for DynamicOccupancyGrid<T> {
fn columns(&self) -> usize {
self.columns
}
fn rows(&self) -> usize {
self.rows
}
fn resolution(&self) -> T {
self.resolution
}
fn origin(&self) -> [T; 2] {
self.origin
}
fn is_occupied(&self, row: usize, column: usize) -> bool {
self.index_of(row, column)
.and_then(|index| self.cells.get(index))
.copied()
.unwrap_or(false)
}
}
#[cfg(feature = "alloc")]
impl<T: Numeric + Primal> MutableOccupancyMap<T> for DynamicOccupancyGrid<T> {
fn set_cell(&mut self, row: usize, column: usize, occupied: bool) {
if let Some(cell) = self
.index_of(row, column)
.and_then(|index| self.cells.get_mut(index))
{
*cell = occupied;
}
}
fn clear(&mut self) {
self.cells.iter_mut().for_each(|cell| *cell = false);
}
}
fn entry_distance<T: Numeric + Primal, M: OccupancyMap<T> + ?Sized>(
map: &M,
start_position: [T; 2],
direction: [T; 2],
) -> Option<T> {
let lowest_corner = map.origin();
let highest_corner = [
lowest_corner[0] + T::from_usize(map.columns()) * map.resolution(),
lowest_corner[1] + T::from_usize(map.rows()) * map.resolution(),
];
let mut near = T::NEG_INFINITY;
let mut far = T::INFINITY;
for (start, step, low, high) in [
(
start_position[0],
direction[0],
lowest_corner[0],
highest_corner[0],
),
(
start_position[1],
direction[1],
lowest_corner[1],
highest_corner[1],
),
] {
if step == T::ZERO {
if start < low || start > high {
return None;
}
} else {
let inverse = T::ONE / step;
let mut first = (low - start) * inverse;
let mut second = (high - start) * inverse;
if first > second {
core::mem::swap(&mut first, &mut second);
}
near = near.max(first);
far = far.min(second);
}
}
if near > far || far < T::ZERO {
return None;
}
Some(near.max(T::ZERO))
}
fn axis_step<T: Numeric>(component: T) -> isize {
if component > T::ZERO { 1 } else { -1 }
}
fn boundary_distance<T: Numeric>(
start_axis: T,
direction_axis: T,
index: isize,
step: isize,
lowest_corner_axis: T,
resolution: T,
) -> T {
if direction_axis == T::ZERO {
return T::INFINITY;
}
let next_index = if step > 0 { index + 1 } else { index };
let boundary = lowest_corner_axis + T::from_f64(next_index as f64) * resolution;
(boundary - start_axis) / direction_axis
}
fn clamp_index<T: Numeric + Primal>(value: T, length: usize) -> isize {
let floored = value.floor().to_f64();
if floored < 0.0 {
0
} else if floored as usize >= length {
length as isize - 1
} else {
floored as isize
}
}