#![deny(clippy::indexing_slicing)]
use crate::error::MappingError;
use crate::scalar::{Numeric, Primal};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScanGeometry<const NUM_BEAMS: usize, T: Numeric = f64> {
field_of_view: T,
minimum_range: T,
maximum_range: T,
}
impl<const NUM_BEAMS: usize, T: Numeric> ScanGeometry<NUM_BEAMS, T> {
pub fn try_new(field_of_view: T, maximum_range: T) -> Result<Self, MappingError> {
if NUM_BEAMS < 2 {
return Err(MappingError::TooFewBeams);
}
if !field_of_view.is_finite() || !maximum_range.is_finite() {
return Err(MappingError::NonFinite);
}
if field_of_view <= T::ZERO || field_of_view > T::TWO_PI {
return Err(MappingError::InvalidFieldOfView);
}
if maximum_range <= T::ZERO {
return Err(MappingError::NonPositiveRange);
}
Ok(ScanGeometry {
field_of_view,
minimum_range: T::ZERO,
maximum_range,
})
}
pub fn with_minimum_range(mut self, minimum_range: T) -> Result<Self, MappingError> {
if !minimum_range.is_finite() {
return Err(MappingError::NonFinite);
}
if minimum_range < T::ZERO || minimum_range >= self.maximum_range {
return Err(MappingError::InvalidRangeLimits);
}
self.minimum_range = minimum_range;
Ok(self)
}
#[inline]
#[must_use]
pub fn field_of_view(&self) -> T {
self.field_of_view
}
#[inline]
#[must_use]
pub fn minimum_range(&self) -> T {
self.minimum_range
}
#[inline]
#[must_use]
pub fn maximum_range(&self) -> T {
self.maximum_range
}
#[inline]
#[must_use]
pub fn num_beams(&self) -> usize {
NUM_BEAMS
}
#[inline]
#[must_use]
pub fn angle_increment(&self) -> T {
self.field_of_view / T::from_usize(NUM_BEAMS - 1)
}
#[inline]
#[must_use]
pub fn range_is_valid(&self, range: T) -> bool {
range.is_finite() && range >= self.minimum_range && range <= self.maximum_range
}
#[inline]
#[must_use]
pub fn beam_angle(&self, index: usize) -> Option<T> {
(index < NUM_BEAMS).then(|| beam_angle_across(self.field_of_view, NUM_BEAMS, index))
}
}
impl<const NUM_BEAMS: usize, T: Numeric + Primal> ScanGeometry<NUM_BEAMS, T> {
#[must_use]
pub fn nearest_beam_index(&self, angle: T) -> Option<usize> {
let half_the_arc = self.field_of_view * T::HALF;
if !angle.is_finite() || angle < -half_the_arc || angle > half_the_arc {
return None;
}
let steps = ((angle + half_the_arc) / self.angle_increment())
.round()
.to_f64();
if steps < 0.0 {
return Some(0);
}
let steps = steps as usize;
Some(steps.min(NUM_BEAMS - 1))
}
}
#[inline]
#[must_use]
pub(crate) fn beam_angle_across<T: Numeric>(field_of_view: T, num_beams: usize, index: usize) -> T {
let span = T::from_usize(num_beams - 1);
-field_of_view * T::HALF + field_of_view * T::from_usize(index) / span
}