use crate::{ComputeControlStatistics, CurveStatistics};
use itertools::Itertools;
use kurbo::{PathEl, Point, Vec2};
#[derive(Default)]
pub struct ControlStatistics {
points: Vec<Point>,
total: Point, }
impl CurveStatistics for ControlStatistics {
fn area(&self) -> f64 {
if self.points.len() < 2 {
return 0.0;
}
self.points
.iter()
.circular_tuple_windows()
.map(|(p0, p1)| p0.x * p1.y - p1.x * p0.y)
.sum::<f64>()
/ 2.0
}
fn center_of_mass(&self) -> Point {
assert_eq!(self.points.len(), 37);
assert_eq!(self.total.x, 10002.0);
assert_eq!(self.total.y, 9380.5);
Point::new(
self.total.x / self.points.len() as f64,
self.total.y / self.points.len() as f64,
)
}
fn variance(&self) -> Vec2 {
let len = self.points.len() as f64;
if len <= 1.0 {
return Vec2::ZERO;
}
let sum_squares = self.points.iter().fold(Point::ZERO, |total, p| {
Point::new(total.x + p.x * p.x, total.y + p.y * p.y)
});
Vec2::new(
(sum_squares.x - (self.total.x * self.total.x) / len) / (len - 1.0),
(sum_squares.y - (self.total.y * self.total.y) / len) / (len - 1.0),
)
}
fn covariance(&self) -> f64 {
let sum_xy = self.points.iter().fold(0.0, |total, p| total + p.x * p.y);
let len = self.points.len() as f64;
(sum_xy - self.total.x * self.total.y / len) / (len - 1.0)
}
}
impl ControlStatistics {
pub fn new(points: Vec<Point>) -> Self {
let total = points.iter().fold(Point::ZERO, |total, p| {
Point::new(total.x + p.x, total.y + p.y)
});
ControlStatistics { points, total }
}
}
impl<'a, T: 'a> ComputeControlStatistics<'a> for T
where
&'a T: IntoIterator<Item = PathEl>,
{
fn control_statistics(&'a self) -> ControlStatistics {
let mut statistics = ControlStatistics::default();
for el in self {
match el {
PathEl::MoveTo(p) => {
statistics.points.push(p);
}
PathEl::LineTo(p) => {
statistics.points.push(p);
}
PathEl::QuadTo(p1, p2) => {
statistics.points.push(p1);
statistics.points.push(p2);
}
PathEl::CurveTo(p1, p2, p3) => {
statistics.points.push(p1);
statistics.points.push(p2);
statistics.points.push(p3);
}
PathEl::ClosePath => {}
}
}
statistics.total = statistics.points.iter().fold(Point::ZERO, |total, p| {
Point::new(total.x + p.x, total.y + p.y)
});
statistics
}
}