use nalgebra::Vector3;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum BezierError {
#[error("Data points must not be zero - got: {0}")]
InvalidNPoints(u64),
#[error("Unreachable data point - y_pos: {0}, z_pos: {1}")]
Unreachable(f64, f64),
}
#[derive(Clone, Debug)]
pub struct Bezier {
pub pos_vec: Vec<Vector3<f64>>,
}
#[derive(Clone, Default, Debug)]
pub struct Vec2D {
pub x: f64,
pub y: f64,
}
impl std::fmt::Display for Vec2D {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "x: {0}, y: {1}", self.x, self.y)
}
}
#[derive(Clone, Default, Debug)]
pub struct BezierBuilder {
pub p0: Vec2D,
pub p1: Vec2D,
pub p2: Vec2D,
pub npoints: Option<u64>,
}
impl BezierBuilder {
pub fn ctrl_pts(mut self, p0: Vec2D, p1: Vec2D, p2: Vec2D) -> Self {
self.p0 = p0;
self.p1 = p1;
self.p2 = p2;
self
}
pub fn npoints(mut self, npoints: u64) -> Self {
self.npoints = Some(npoints);
self
}
pub fn build(self) -> Result<Bezier, BezierError> {
let Some(npoints) = self.npoints else {
return Err(BezierError::InvalidNPoints(self.npoints.unwrap()));
};
let t_step: f64 = 1.0 / (npoints - 1) as f64;
let mut t: Vec<f64> = Vec::new();
for i in 0..npoints {
t.push(i as f64 * t_step)
}
let pos_vec = t
.iter()
.map(|t| {
let y_pos: f64 = ((1.0 - t).powf(2.0) * self.p0.x)
+ ((2.0 * (1.0 - t) * t) * self.p1.x)
+ (t.powf(2.0) * self.p2.x);
let z_pos: f64 = ((1.0 - t).powf(2.0) * self.p0.y)
+ ((2.0 * (1.0 - t) * t) * self.p1.y)
+ (t.powf(2.0) * self.p2.y);
if !(y_pos.is_finite() && z_pos.is_finite()) {
return Err(BezierError::Unreachable(y_pos, z_pos));
}
Ok(Vector3::new(0.0, y_pos, z_pos))
})
.collect::<Result<Vec<Vector3<f64>>, BezierError>>()?;
Ok(Bezier { pos_vec })
}
}