use crate::{util::*, *};
use alloc::{format, vec::Vec};
use core::f64::consts::{PI, TAU};
#[cfg(not(feature = "std"))]
use num_traits::*;
macro_rules! harmonic {
($is_open:ident, $curve1:ident $(, $curve2:ident)*) => {{
let len = $curve1.len()$(.min($curve2.len()))*;
if $is_open { len * 2 } else { len }
}};
}
pub(crate) use harmonic;
pub fn get_target_pos<C, const D: usize>(curve: C, is_open: bool) -> (Vec<f64>, GeoVar<Rot<D>, D>)
where
C: Curve<D>,
U<D>: EfdDim<D>,
{
let (pos, [(mut coeff, geo)]) = U::<D>::get_coeff([curve.as_curve()], is_open, 1);
(pos, geo * U::<D>::coeff_norm(&mut coeff))
}
pub type Efd1 = Efd<1>;
pub type Efd2 = Efd<2>;
pub type Efd3 = Efd<3>;
pub struct Efd<const D: usize>
where
U<D>: EfdDim<D>,
{
coeffs: Coeffs<D>,
geo: GeoVar<Rot<D>, D>,
}
impl<const D: usize> Efd<D>
where
U<D>: EfdDim<D>,
{
pub const fn from_parts_unchecked(coeffs: Coeffs<D>, geo: GeoVar<Rot<D>, D>) -> Self {
Self { coeffs, geo }
}
pub fn try_from_coeffs(mut coeffs: Coeffs<D>) -> Option<Self> {
(!coeffs.is_empty()).then(|| Self { geo: U::<D>::coeff_norm(&mut coeffs), coeffs })
}
pub fn try_from_coeffs_unnorm(coeffs: Coeffs<D>) -> Option<Self> {
(!coeffs.is_empty()).then_some(Self { coeffs, geo: GeoVar::identity() })
}
#[must_use]
pub fn from_curve<C>(curve: C, is_open: bool) -> Self
where
C: Curve<D>,
{
let harmonic = harmonic!(is_open, curve);
Self::from_curve_harmonic(curve, is_open, harmonic).fourier_power_anaysis(None)
}
#[must_use]
pub fn from_curve_nyquist<C>(curve: C, is_open: bool) -> Self
where
C: Curve<D>,
{
let len = curve.len();
Self::from_curve_harmonic(curve, is_open, if is_open { len } else { len / 2 })
.fourier_power_anaysis(None)
}
#[must_use]
pub fn from_curve_harmonic<C>(curve: C, is_open: bool, harmonic: usize) -> Self
where
C: Curve<D>,
{
Self::from_curve_harmonic_and_get(curve, is_open, harmonic).0
}
#[must_use]
pub fn from_curve_harmonic_and_get<C>(
curve: C,
is_open: bool,
harmonic: usize,
) -> (Self, Vec<f64>)
where
C: Curve<D>,
{
debug_assert!(harmonic != 0, "harmonic must not be 0");
debug_assert!(curve.len() > 2, "the curve length must greater than 2");
let curve = curve.as_curve();
let (pos, [(mut coeffs, geo1)]) = U::<D>::get_coeff([curve], is_open, harmonic);
let geo2 = U::<D>::coeff_norm(&mut coeffs);
(Self { coeffs, geo: geo1 * geo2 }, pos)
}
#[must_use]
pub fn from_curve_harmonic_unnorm<C>(curve: C, is_open: bool, harmonic: usize) -> Self
where
C: Curve<D>,
{
debug_assert!(harmonic != 0, "harmonic must not be 0");
debug_assert!(curve.len() > 2, "the curve length must greater than 2");
let curve = curve.as_curve();
let (_, [(coeffs, geo)]) = U::<D>::get_coeff([curve], is_open, harmonic);
Self { coeffs, geo }
}
#[must_use]
pub fn with_geo(self, geo: GeoVar<Rot<D>, D>) -> Self {
Self { geo, ..self }
}
#[must_use]
pub fn fourier_power_anaysis<T>(mut self, threshold: T) -> Self
where
Option<f64>: From<T>,
{
let lut = self.coeffs.iter().map(|m| m.map(pow2).sum()).collect();
self.set_harmonic(fourier_power_anaysis(lut, threshold));
self
}
pub fn set_harmonic(&mut self, harmonic: usize) {
let current = self.harmonic();
assert!(
(1..=current).contains(&harmonic),
"harmonic must in 1..={current}"
);
self.coeffs.resize_with(harmonic, Kernel::zeros);
}
pub fn normalized(self) -> Self {
let Self { mut coeffs, geo } = self;
let geo_new = U::<D>::coeff_norm(&mut coeffs);
Self { coeffs, geo: geo.apply(&geo_new) }
}
#[must_use]
pub fn into_inner(self) -> (Coeffs<D>, GeoVar<Rot<D>, D>) {
(self.coeffs, self.geo)
}
#[must_use]
pub fn coeffs(&self) -> &[Kernel<D>] {
&self.coeffs
}
#[must_use]
pub fn coeff(&self, harmonic: usize) -> &Kernel<D> {
&self.coeffs[harmonic]
}
pub fn coeffs_iter(&self) -> impl Iterator<Item = &Kernel<D>> {
self.coeffs.iter()
}
pub fn coeffs_iter_mut(&mut self) -> impl Iterator<Item = &mut Kernel<D>> {
self.coeffs.iter_mut()
}
#[must_use]
pub fn as_geo(&self) -> &GeoVar<Rot<D>, D> {
&self.geo
}
#[must_use]
pub fn as_geo_mut(&mut self) -> &mut GeoVar<Rot<D>, D> {
&mut self.geo
}
#[must_use]
pub fn is_open(&self) -> bool {
self.coeffs[0][(1, 0)] == 0.
}
#[must_use]
pub fn harmonic(&self) -> usize {
self.coeffs.len()
}
#[must_use]
pub fn is_valid(&self) -> bool {
!self.coeffs.is_empty()
&& !self
.coeffs_iter()
.any(|m| m.iter().all(|x| x.abs() < f64::EPSILON) || m.iter().any(|x| x.is_nan()))
}
#[must_use]
pub fn distance(&self, rhs: &Self) -> f64 {
self.l1_norm(rhs)
}
pub fn reverse_inplace(&mut self) {
self.coeffs.iter_mut().for_each(|m| {
let mut m = m.column_mut(1);
m *= -1.
});
}
#[must_use]
pub fn reversed(mut self) -> Self {
self.reverse_inplace();
self
}
#[must_use]
pub fn generate(&self, n: usize) -> Vec<Coord<D>> {
self.generate_in(n, TAU)
}
#[must_use]
pub fn generate_half(&self, n: usize) -> Vec<Coord<D>> {
self.generate_in(n, PI)
}
fn generate_in(&self, n: usize, theta: f64) -> Vec<Coord<D>> {
let mut curve = self.generate_norm_in(n, theta);
self.geo.transform_inplace(&mut curve);
curve
}
#[must_use]
pub fn generate_norm(&self, n: usize) -> Vec<Coord<D>> {
self.generate_norm_in(n, TAU)
}
#[must_use]
pub fn generate_norm_half(&self, n: usize) -> Vec<Coord<D>> {
self.generate_norm_in(n, PI)
}
fn generate_norm_in(&self, n: usize, theta: f64) -> Vec<Coord<D>> {
debug_assert!(n > 2, "n ({n}) must larger than 2");
let t = na::Matrix1xX::from_fn(n, |_, i| i as f64 / (n - 1) as f64 * theta);
U::<D>::reconstruct(&self.coeffs, t)
}
#[must_use]
pub fn generate_by(&self, t: &[f64]) -> Vec<Coord<D>> {
let mut curve = U::<D>::reconstruct(&self.coeffs, na::Matrix1xX::from_column_slice(t));
self.geo.transform_inplace(&mut curve);
curve
}
#[must_use]
pub fn generate_norm_by(&self, t: &[f64]) -> Vec<Coord<D>> {
U::<D>::reconstruct(&self.coeffs, na::Matrix1xX::from_column_slice(t))
}
}
impl<const D: usize> Clone for Efd<D>
where
U<D>: EfdDim<D>,
{
fn clone(&self) -> Self {
Self { coeffs: self.coeffs.clone(), geo: self.geo.clone() }
}
}
impl<const D: usize> core::fmt::Debug for Efd<D>
where
U<D>: EfdDim<D>,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.is_valid() {
f.debug_struct(&format!("Efd{D}"))
.field("is_open", &self.is_open())
.field("harmonic", &self.harmonic())
.field("geo", &self.geo)
.field("coeff", &CoeffFmt(&self.coeffs))
.finish()
} else {
f.debug_struct(&format!("Efd{D}"))
.field("is_valid", &false)
.finish()
}
}
}
impl<const D: usize> core::fmt::Debug for PosedEfd<D>
where
U<D>: EfdDim<D>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(&format!("PosedEfd{D}"))
.field("is_open", &self.is_open())
.field("harmonic", &self.harmonic())
.field("curve_geo", &self.curve_efd().geo)
.field("curve_coeff", &CoeffFmt(&self.curve_efd().coeffs))
.field("pose_geo", &self.pose_efd().geo)
.field("pose_coeff", &CoeffFmt(&self.pose_efd().coeffs))
.finish()
}
}
struct CoeffFmt<'a, const D: usize>(&'a Coeffs<D>);
impl<const D: usize> core::fmt::Debug for CoeffFmt<'_, D> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let entries = self.0.iter().map(|c| c.iter().copied().collect::<Vec<_>>());
f.debug_list().entries(entries).finish()
}
}
pub(crate) fn fourier_power_anaysis<T>(lut: Vec<f64>, threshold: T) -> usize
where
Option<f64>: From<T>,
{
let threshold = Option::from(threshold).unwrap_or(0.9999);
assert!((0.0..1.0).contains(&threshold), "threshold must in 0..1");
let lut = cumsum(na::Matrix1xX::from_vec(lut));
let target = lut[lut.len() - 1] * threshold;
match lut
.as_slice()
.binary_search_by(|x| x.partial_cmp(&target).unwrap())
{
Ok(h) | Err(h) => h + 1,
}
}