efd/
posed.rs

1//! Posed EFD (Elliptic Fourier Descriptor) is a special shape to describe the
2//! pose of a curve. It is a combination of two curves, the first is the
3//! original curve, and the second is the pose unit vectors.
4//!
5//! Please see the [`PosedEfd`] type for more information.
6use crate::*;
7use alloc::vec::Vec;
8use core::{array, iter::zip};
9#[cfg(not(feature = "std"))]
10#[allow(unused_imports)]
11use num_traits::*;
12
13/// A 1D shape with a pose described by EFD.
14pub type PosedEfd1 = PosedEfd<1>;
15/// A 2D shape with a pose described by EFD.
16pub type PosedEfd2 = PosedEfd<2>;
17/// A 3D shape with a pose described by EFD.
18pub type PosedEfd3 = PosedEfd<3>;
19
20/// Transform 2D angles to unit vectors.
21/// ```
22/// # let angles = efd::tests::ANGLE2D_POSE;
23/// let vectors = efd::posed::ang2vec(angles);
24/// ```
25pub fn ang2vec(angles: &[f64]) -> Vec<[f64; 2]> {
26    angles.iter().map(|a| [a.cos(), a.sin()]).collect()
27}
28
29/// Create a guide curve from a curve and its unit vectors.
30/// ```
31/// # let curve_p = efd::tests::CURVE2D_POSE;
32/// # let vectors = efd::posed::ang2vec(efd::tests::ANGLE2D_POSE);
33/// // A custom length
34/// let length = 20.;
35/// let curve_q = efd::posed::guide_from_curve(curve_p, vectors, length);
36/// let efd = efd::posed::PosedEfd2::from_series(curve_p, curve_q, true);
37/// ```
38/// See also [`PosedEfd::from_uvec()`] / [`PosedEfd::from_series()`].
39pub fn guide_from_curve<C, V, const D: usize>(curve: C, vectors: V, length: f64) -> Vec<[f64; D]>
40where
41    C: Curve<D>,
42    V: Curve<D>,
43{
44    zip(curve.as_curve(), vectors.as_curve())
45        .map(|(p, v)| core::array::from_fn(|i| p[i] + length * v[i]))
46        .collect()
47}
48
49/// Motion signature with the target position.
50///
51/// Used to present an "original motion". Can be compared with [`PosedEfd`] by
52/// [`PosedEfd::err_sig()`].
53#[derive(Clone)]
54pub struct MotionSig<const D: usize>
55where
56    U<D>: EfdDim<D>,
57{
58    /// Normalized curve
59    pub curve: Vec<[f64; D]>,
60    /// Normalized unit vectors
61    pub vectors: Vec<[f64; D]>,
62    /// Normalized time parameters
63    pub t: Vec<f64>,
64    /// Geometric variables
65    pub geo: GeoVar<Rot<D>, D>,
66}
67
68impl<const D: usize> MotionSig<D>
69where
70    U<D>: EfdDim<D>,
71{
72    /// Get the path signature and its target position from a curve and its unit
73    /// vectors.
74    ///
75    /// This function is faster than building [`PosedEfd`] since it only
76    /// calculates **two harmonics**.
77    /// ```
78    /// use efd::posed::{ang2vec, MotionSig};
79    /// # let curve = efd::tests::CURVE2D_POSE;
80    /// # let angles = efd::tests::ANGLE2D_POSE;
81    ///
82    /// let sig = MotionSig::new(curve, ang2vec(angles), true);
83    /// ```
84    /// See also [`PathSig`].
85    pub fn new<C, V>(curve: C, vectors: V, is_open: bool) -> Self
86    where
87        C: Curve<D>,
88        V: Curve<D>,
89    {
90        let PathSig { curve, t, geo } = PathSig::new(curve.as_curve(), is_open);
91        let mut vectors = geo.inverse().only_rot().transform(vectors);
92        for (p, v) in zip(&curve, &mut vectors) {
93            *v = array::from_fn(|i| p[i] + v[i]);
94        }
95        Self { curve, vectors, t, geo }
96    }
97
98    /// Get the reference of normalized time parameters.
99    pub fn as_t(&self) -> &[f64] {
100        &self.t
101    }
102
103    /// Get the reference of geometric variables.
104    pub fn as_geo(&self) -> &GeoVar<Rot<D>, D> {
105        &self.geo
106    }
107}
108
109/// An open-curve shape with a pose described by EFD.
110///
111/// These are the same as [`Efd`] except that it has a pose, and the data are
112/// always normalized and readonly.
113///
114/// Start with [`PosedEfd::from_angles()`] / [`PosedEfd::from_series()`] /
115/// [`PosedEfd::from_uvec()`] and their related methods.
116///
117/// # Pose Representation
118/// Pose is represented by an unit vector, which is rotated by the rotation
119/// of the original shape.
120#[derive(Clone)]
121pub struct PosedEfd<const D: usize>
122where
123    U<D>: EfdDim<D>,
124{
125    curve: Efd<D>,
126    pose: Efd<D>,
127}
128
129impl PosedEfd2 {
130    /// Calculate the coefficients from a curve and its angles from each point.
131    pub fn from_angles<C>(curve: C, angles: &[f64], is_open: bool) -> Self
132    where
133        C: Curve<2>,
134    {
135        let harmonic = harmonic(false, curve.len());
136        Self::from_angles_harmonic(curve, angles, is_open, harmonic).fourier_power_anaysis(None)
137    }
138
139    /// Calculate the coefficients from a curve and its angles from each point.
140    ///
141    /// The `harmonic` is the number of the coefficients to be calculated.
142    pub fn from_angles_harmonic<C>(curve: C, angles: &[f64], is_open: bool, harmonic: usize) -> Self
143    where
144        C: Curve<2>,
145    {
146        Self::from_uvec_harmonic(curve, ang2vec(angles), is_open, harmonic)
147    }
148}
149
150impl<const D: usize> PosedEfd<D>
151where
152    U<D>: EfdDim<D>,
153{
154    /// Create object from an [`Efd`] object.
155    ///
156    /// Posed EFD is a special shape to describe the pose, `efd` is only used to
157    /// describe this motion signature.
158    ///
159    /// See also [`PosedEfd::into_inner()`].
160    pub const fn from_parts_unchecked(curve: Efd<D>, pose: Efd<D>) -> Self {
161        Self { curve, pose }
162    }
163
164    /// Calculate the coefficients from two series of points.
165    ///
166    /// The second series is the pose series, the `curve_q[i]` has the same time
167    /// as `curve_p[i]`.
168    pub fn from_series<C1, C2>(curve_p: C1, curve_q: C2, is_open: bool) -> Self
169    where
170        C1: Curve<D>,
171        C2: Curve<D>,
172    {
173        let harmonic = harmonic(true, curve_p.len());
174        Self::from_series_harmonic(curve_p, curve_q, is_open, harmonic).fourier_power_anaysis(None)
175    }
176
177    /// Calculate the coefficients from two series of points.
178    ///
179    /// The `harmonic` is the number of the coefficients to be calculated.
180    pub fn from_series_harmonic<C1, C2>(
181        curve_p: C1,
182        curve_q: C2,
183        is_open: bool,
184        harmonic: usize,
185    ) -> Self
186    where
187        C1: Curve<D>,
188        C2: Curve<D>,
189    {
190        let vectors = zip(curve_p.as_curve(), curve_q.as_curve())
191            .map(|(p, q)| array::from_fn(|i| q[i] - p[i]))
192            .collect::<Vec<_>>();
193        Self::from_uvec_harmonic(curve_p, vectors, is_open, harmonic)
194    }
195
196    /// Calculate the coefficients from a curve and its unit vectors from each
197    /// point.
198    ///
199    /// If the unit vectors is not normalized, the length of the first vector
200    /// will be used as the scaling factor.
201    pub fn from_uvec<C, V>(curve: C, vectors: V, is_open: bool) -> Self
202    where
203        C: Curve<D>,
204        V: Curve<D>,
205    {
206        let harmonic = harmonic(true, curve.len());
207        Self::from_uvec_harmonic(curve, vectors, is_open, harmonic).fourier_power_anaysis(None)
208    }
209
210    /// Calculate the coefficients from a curve and its unit vectors from each
211    /// point.
212    ///
213    /// If the unit vectors is not normalized, the length of the first vector
214    /// will be used as the scaling factor.
215    ///
216    /// The `harmonic` is the number of the coefficients to be calculated.
217    pub fn from_uvec_harmonic<C, V>(curve: C, vectors: V, is_open: bool, harmonic: usize) -> Self
218    where
219        C: Curve<D>,
220        V: Curve<D>,
221    {
222        debug_assert!(harmonic != 0, "harmonic must not be 0");
223        debug_assert!(curve.len() > 2, "the curve length must greater than 2");
224        debug_assert!(
225            curve.len() == vectors.len(),
226            "the curve length must be equal to the vectors length"
227        );
228        let curve = curve.as_curve();
229        let guide = {
230            let dxyz = util::diff(if !is_open && curve.first() != curve.last() {
231                to_mat(curve.closed_lin())
232            } else {
233                to_mat(curve)
234            });
235            dxyz.map(util::pow2).row_sum().map(f64::sqrt)
236        };
237        let (_, mut coeffs, geo) = U::get_coeff(curve, is_open, harmonic, Some(guide.as_slice()));
238        let geo = geo * U::norm_coeff(&mut coeffs, None);
239        let geo_inv = geo.inverse();
240        let p_norm = geo_inv.transform(curve);
241        let mut q_norm = geo_inv.only_rot().transform(vectors);
242        for (p, q) in zip(p_norm, &mut q_norm) {
243            *q = array::from_fn(|i| p[i] + q[i]);
244        }
245        let curve = Efd::from_parts_unchecked(coeffs, geo);
246        let (_, mut coeffs, q_trans) =
247            U::get_coeff(&q_norm, is_open, harmonic, Some(guide.as_slice()));
248        U::norm_zeta(&mut coeffs, None);
249        let pose = Efd::from_parts_unchecked(coeffs, q_trans);
250        Self { curve, pose }
251    }
252
253    /// Use Fourier Power Anaysis (FPA) to reduce the harmonic number.
254    ///
255    /// The posed EFD will set the harmonic number to the maximum harmonic
256    /// number of the curve and the pose.
257    ///
258    /// See also [`Efd::fourier_power_anaysis()`].
259    ///
260    /// # Panics
261    /// Panics if the threshold is not in 0..1, or the harmonic is zero.
262    pub fn fourier_power_anaysis(mut self, threshold: impl Into<Option<f64>>) -> Self {
263        self.fpa_inplace(threshold);
264        self
265    }
266
267    /// Fourier Power Anaysis (FPA) function with in-place operation.
268    ///
269    /// See also [`PosedEfd::fourier_power_anaysis()`].
270    ///
271    /// # Panics
272    /// Panics if the threshold is not in 0..1, or the harmonic is zero.
273    pub fn fpa_inplace(&mut self, threshold: impl Into<Option<f64>>) {
274        let threshold = threshold.into();
275        let [harmonic1, harmonic2] = [&self.curve, &self.pose].map(|efd| {
276            let lut = efd.coeffs_iter().map(|m| m.map(util::pow2).sum()).collect();
277            fourier_power_anaysis(lut, threshold)
278        });
279        self.set_harmonic(harmonic1.max(harmonic2));
280    }
281
282    /// Set the harmonic number of the coefficients.
283    ///
284    /// See also [`Efd::set_harmonic()`].
285    ///
286    /// # Panics
287    /// Panics if the harmonic is zero.
288    pub fn set_harmonic(&mut self, harmonic: usize) {
289        self.curve.set_harmonic(harmonic);
290        self.pose.set_harmonic(harmonic);
291    }
292
293    /// Consume self and return the parts of this type. The first is the curve
294    /// coefficients, and the second is the pose coefficients.
295    ///
296    /// See also [`PosedEfd::from_parts_unchecked()`].
297    pub fn into_inner(self) -> (Efd<D>, Efd<D>) {
298        (self.curve, self.pose)
299    }
300
301    /// Get the reference of the curve coefficients.
302    ///
303    /// **Note**: There is no mutable reference, please use
304    /// [`PosedEfd::into_inner()`] instead.
305    /// ```
306    /// # let curve = efd::tests::CURVE2D_POSE;
307    /// # let angles = efd::tests::ANGLE2D_POSE;
308    /// let efd = efd::PosedEfd2::from_angles(curve, angles, true);
309    /// let curve_efd = efd.as_curve();
310    /// let (mut curve_efd, _) = efd.into_inner();
311    /// ```
312    /// See also [`PosedEfd::as_pose()`].
313    pub fn as_curve(&self) -> &Efd<D> {
314        &self.curve
315    }
316
317    /// Get the reference of the pose coefficients.
318    ///
319    /// **Note**: There is no mutable reference, please use
320    /// [`PosedEfd::into_inner()`] instead.
321    /// ```
322    /// # let curve = efd::tests::CURVE2D_POSE;
323    /// # let angles = efd::tests::ANGLE2D_POSE;
324    /// let efd = efd::PosedEfd2::from_angles(curve, angles, true);
325    /// let pose_efd = efd.as_pose();
326    /// let (_, mut pose_efd) = efd.into_inner();
327    /// ```
328    /// See also [`PosedEfd::as_curve()`].
329    pub fn as_pose(&self) -> &Efd<D> {
330        &self.pose
331    }
332
333    /// Check if the descibed motion is open.
334    pub fn is_open(&self) -> bool {
335        self.curve.is_open()
336    }
337
338    /// Get the harmonic number of the coefficients.
339    ///
340    /// The curve and the pose coefficients are always have the same harmonic
341    /// number.
342    #[inline]
343    pub fn harmonic(&self) -> usize {
344        self.curve.harmonic()
345    }
346
347    /// Calculate the error between two [`PosedEfd`].
348    pub fn err(&self, rhs: &Self) -> f64 {
349        (2. * self.curve.err(&rhs.curve))
350            .max(self.pose.err(&rhs.pose))
351            .max((self.pose.as_geo().trans()).l2_err(rhs.pose.as_geo().trans()))
352    }
353
354    /// Calculate the error from a [`MotionSig`].
355    pub fn err_sig(&self, sig: &MotionSig<D>) -> f64 {
356        let curve =
357            zip(self.curve.recon_norm_by(&sig.t), &sig.curve).map(|(a, b)| 2. * a.l2_err(b));
358        let pose = zip(self.pose.recon_by(&sig.t), &sig.vectors).map(|(a, b)| a.l2_err(b));
359        curve.chain(pose).fold(0., f64::max)
360    }
361}