use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct BezierCurve<S: ControlScalar, const DIM: usize> {
pub control: [[S; DIM]; 4],
}
impl<S: ControlScalar, const DIM: usize> BezierCurve<S, DIM> {
pub fn new(p0: [S; DIM], p1: [S; DIM], p2: [S; DIM], p3: [S; DIM]) -> Self {
Self {
control: [p0, p1, p2, p3],
}
}
pub fn line(p0: [S; DIM], p1: [S; DIM]) -> Self {
let third = S::ONE / S::from_f64(3.0);
let two_thirds = S::TWO / S::from_f64(3.0);
let cp1 = core::array::from_fn(|i| p0[i] + third * (p1[i] - p0[i]));
let cp2 = core::array::from_fn(|i| p0[i] + two_thirds * (p1[i] - p0[i]));
Self::new(p0, cp1, cp2, p1)
}
pub fn evaluate(&self, t: S) -> [S; DIM] {
let t = t.clamp_val(S::ZERO, S::ONE);
let mt = S::ONE - t;
let mt2 = mt * mt;
let mt3 = mt2 * mt;
let t2 = t * t;
let t3 = t2 * t;
let three = S::from_f64(3.0);
core::array::from_fn(|i| {
mt3 * self.control[0][i]
+ three * mt2 * t * self.control[1][i]
+ three * mt * t2 * self.control[2][i]
+ t3 * self.control[3][i]
})
}
pub fn derivative(&self, t: S) -> [S; DIM] {
let t = t.clamp_val(S::ZERO, S::ONE);
let mt = S::ONE - t;
let mt2 = mt * mt;
let t2 = t * t;
let three = S::from_f64(3.0);
let two = S::TWO;
core::array::from_fn(|i| {
three
* (mt2 * (self.control[1][i] - self.control[0][i])
+ two * mt * t * (self.control[2][i] - self.control[1][i])
+ t2 * (self.control[3][i] - self.control[2][i]))
})
}
pub fn second_derivative(&self, t: S) -> [S; DIM] {
let t = t.clamp_val(S::ZERO, S::ONE);
let mt = S::ONE - t;
let six = S::from_f64(6.0);
let two = S::TWO;
core::array::from_fn(|i| {
six * (mt * (self.control[2][i] - two * self.control[1][i] + self.control[0][i])
+ t * (self.control[3][i] - two * self.control[2][i] + self.control[1][i]))
})
}
pub fn split(&self, t: S) -> (Self, Self) {
let t = t.clamp_val(S::ZERO, S::ONE);
let mt = S::ONE - t;
let q0: [S; DIM] =
core::array::from_fn(|i| mt * self.control[0][i] + t * self.control[1][i]);
let q1: [S; DIM] =
core::array::from_fn(|i| mt * self.control[1][i] + t * self.control[2][i]);
let q2: [S; DIM] =
core::array::from_fn(|i| mt * self.control[2][i] + t * self.control[3][i]);
let r0: [S; DIM] = core::array::from_fn(|i| mt * q0[i] + t * q1[i]);
let r1: [S; DIM] = core::array::from_fn(|i| mt * q1[i] + t * q2[i]);
let s: [S; DIM] = core::array::from_fn(|i| mt * r0[i] + t * r1[i]);
(
Self::new(self.control[0], q0, r0, s),
Self::new(s, r1, q2, self.control[3]),
)
}
pub fn arc_length(&self) -> S {
let n = 16usize;
let dt = S::ONE / S::from_f64(n as f64);
let mut length = S::ZERO;
let mut prev = self.evaluate(S::ZERO);
for i in 1..=n {
let t = S::from_f64(i as f64) * dt;
let curr = self.evaluate(t);
let seg_len: S = (0..DIM)
.map(|d| (curr[d] - prev[d]) * (curr[d] - prev[d]))
.fold(S::ZERO, |a, b| a + b)
.sqrt();
length += seg_len;
prev = curr;
}
length
}
}
#[derive(Debug, Clone, Copy)]
pub struct BezierPath<S: ControlScalar, const DIM: usize, const SEGS: usize> {
pub segments: [BezierCurve<S, DIM>; SEGS],
}
impl<S: ControlScalar, const DIM: usize, const SEGS: usize> BezierPath<S, DIM, SEGS> {
pub fn new(segments: [BezierCurve<S, DIM>; SEGS]) -> Self {
Self { segments }
}
pub fn evaluate(&self, t: S) -> [S; DIM] {
let t_clamped = t.clamp_val(S::ZERO, S::from_f64(SEGS as f64));
let seg_f = t_clamped.min(S::from_f64((SEGS - 1) as f64));
let seg_idx = {
let mut idx = 0usize;
for i in 0..SEGS {
if seg_f >= S::from_f64(i as f64) {
idx = i;
}
}
idx
};
let local_t = t_clamped - S::from_f64(seg_idx as f64);
self.segments[seg_idx].evaluate(local_t)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn endpoints_interpolated() {
let p0 = [0.0_f64, 0.0];
let p1 = [1.0, 2.0];
let p2 = [2.0, 2.0];
let p3 = [3.0, 0.0];
let c = BezierCurve::new(p0, p1, p2, p3);
let start = c.evaluate(0.0);
let end = c.evaluate(1.0);
assert!((start[0] - 0.0).abs() < 1e-10 && (start[1] - 0.0).abs() < 1e-10);
assert!((end[0] - 3.0).abs() < 1e-10 && (end[1] - 0.0).abs() < 1e-10);
}
#[test]
fn line_is_straight() {
let c = BezierCurve::line([0.0_f64, 0.0], [4.0, 0.0]);
let mid = c.evaluate(0.5);
assert!((mid[0] - 2.0).abs() < 1e-10, "mid_x={}", mid[0]);
assert!(mid[1].abs() < 1e-10, "mid_y={}", mid[1]);
}
#[test]
fn derivative_at_start() {
let p0 = [0.0_f64, 0.0];
let p1 = [1.0, 0.0];
let p2 = [2.0, 0.0];
let p3 = [3.0, 0.0];
let c = BezierCurve::new(p0, p1, p2, p3);
let d = c.derivative(0.0);
assert!((d[0] - 3.0).abs() < 1e-10, "d[0]={}", d[0]);
assert!(d[1].abs() < 1e-10);
}
#[test]
fn split_reconstructs_curve() {
let c = BezierCurve::new([0.0_f64, 0.0], [1.0, 2.0], [2.0, 2.0], [3.0, 0.0]);
let (c1, c2) = c.split(0.5);
let orig = c.evaluate(0.5);
let split_pt1 = c1.evaluate(1.0);
let split_pt2 = c2.evaluate(0.0);
for d in 0..2 {
assert!(
(split_pt1[d] - orig[d]).abs() < 1e-10,
"d={} split1={}",
d,
split_pt1[d]
);
assert!(
(split_pt2[d] - orig[d]).abs() < 1e-10,
"d={} split2={}",
d,
split_pt2[d]
);
}
}
#[test]
fn arc_length_line() {
let c = BezierCurve::line([0.0_f64, 0.0], [3.0, 4.0]);
let len = c.arc_length();
assert!((len - 5.0).abs() < 0.01, "len={}", len);
}
#[test]
fn clamp_outside_range() {
let c = BezierCurve::line([0.0_f64], [1.0]);
assert!((c.evaluate(-1.0)[0] - 0.0).abs() < 1e-10);
assert!((c.evaluate(2.0)[0] - 1.0).abs() < 1e-10);
}
}