use crate::calc_spline;
use crate::{Error, Result, SplineOpts, TryFrom};
pub const DEFAULT_APPROX_EQ_PRECISION: f64 = 1e-6;
#[derive(Clone, Default, Debug)]
pub struct Point {
pub x: f64,
pub y: f64,
pub tension: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Points(Vec<Point>);
impl Point {
pub fn new(x: f64, y: f64) -> Self {
Point {
x,
y,
tension: None,
}
}
pub fn with_tension(x: f64, y: f64, tension: f64) -> Self {
Point {
x,
y,
tension: Some(tension),
}
}
pub fn approx_eq(&self, other: &Point) -> bool {
((self.x - other.x).abs() < DEFAULT_APPROX_EQ_PRECISION)
&& ((self.y - other.y).abs() < DEFAULT_APPROX_EQ_PRECISION)
}
pub fn approx_eq_with_precision(&self, other: &Point, precision: f64) -> bool {
((self.x - other.x).abs() < precision) && ((self.y - other.y).abs() < precision)
}
pub fn invert_horizontally(&mut self, width: f64) {
self.x = width - self.x;
}
pub fn invert_vertically(&mut self, height: f64) {
self.y = height - self.y;
}
}
impl From<(f64, f64)> for Point {
fn from(p: (f64, f64)) -> Self {
Point::new(p.0, p.1)
}
}
impl<'a> From<&'a Point> for Point {
fn from(p: &'a Point) -> Self {
p.clone()
}
}
impl<'a, T: Copy> From<&'a T> for Point
where
T: Into<Point>,
{
fn from(p: &'a T) -> Self {
(*p).into()
}
}
impl<T> From<[T; 2]> for Point
where
(T, T): Into<Point>,
{
fn from([x, y]: [T; 2]) -> Self {
(x, y).into()
}
}
impl Points {
pub fn get_ref(&self) -> &Vec<Point> {
&self.0
}
pub fn get_mut(&mut self) -> &mut Vec<Point> {
&mut self.0
}
pub fn into_inner(self) -> Vec<Point> {
self.0
}
pub fn try_from_flatten<'a, I: IntoIterator<Item = &'a f64>>(into_f64_iter: I) -> Result<Self> {
let mut v = Vec::new();
let mut x = None;
for point in into_f64_iter.into_iter() {
if let Some(px) = x {
v.push(Point::new(px, *point));
x = None;
} else {
x = Some(*point);
}
}
if x.is_some() {
return Err(Error::MissingY);
}
if v.len() < 2 {
return Err(Error::TooFewPoints);
}
Ok(Points(v))
}
pub fn invert_horizontally(&mut self, width: f64) {
self.0.iter_mut().for_each(|p| p.invert_horizontally(width));
}
pub fn invert_vertically(&mut self, height: f64) {
self.0.iter_mut().for_each(|p| p.invert_vertically(height));
}
pub fn calc_spline(&self, opts: &SplineOpts) -> Result<Points> {
calc_spline(&self, opts)
}
}
impl<I: IntoIterator> From<I> for Points
where
I::Item: Into<Point>,
{
fn from(points: I) -> Self {
Points(points.into_iter().map(Into::into).collect())
}
}
impl<I: IntoIterator> TryFrom<I> for Points
where
I::Item: Into<Point>,
{
type Error = Error;
fn try_from(points: I) -> Result<Self> {
let v: Vec<Point> = points.into_iter().map(Into::into).collect();
if v.len() < 2 {
return Err(Error::TooFewPoints);
}
Ok(Points(v))
}
}
impl From<Points> for Vec<(f64, f64)> {
fn from(pts: Points) -> Self {
pts.get_ref().iter().map(|p| (p.x, p.y)).collect()
}
}
impl From<Points> for Vec<[f64; 2]> {
fn from(pts: Points) -> Self {
pts.get_ref().iter().map(|p| [p.x, p.y]).collect()
}
}
impl From<Points> for Vec<f64> {
fn from(pts: Points) -> Self {
let mut res = Vec::with_capacity(pts.0.len() * 2);
pts.get_ref().iter().for_each(|p| {
res.push(p.x);
res.push(p.y);
});
res
}
}
#[cfg(test)]
mod test {
use crate::{Error, Points, TryFrom};
fn points_eq(pp1: &Points, pp2: &Points) -> bool {
pp1
.get_ref()
.iter()
.zip(pp2.get_ref().iter())
.all(|(p1, p2)| p1.approx_eq_with_precision(p2, 0.000_1))
}
#[test]
fn from() {
let src1 = vec![[1.2, 3.3], [122.2, 333.3]];
let src2 = [[1.2, 3.3], [122.2, 333.3]];
let src3 = [(1.2, 3.3), (122.2, 333.3)];
let src4 = [1.2, 3.3, 122.2, 333.3];
assert!(points_eq(&Points::from(&src1), &Points::from(&src2)));
assert!(points_eq(&Points::from(&src1), &Points::from(&src3)));
assert!(points_eq(
&Points::from(&src1),
&Points::try_from_flatten(&src4).unwrap(),
));
let src5 = vec![[1.2, 3.3]];
let src6 = [1.2, 3.3, 122.2];
assert_eq!(Points::try_from(&src5).unwrap_err(), Error::TooFewPoints);
assert_eq!(
Points::try_from_flatten(&src6).unwrap_err(),
Error::MissingY
);
}
}