use geo_nd::vector;
use geo_nd::{FArray, Float, Vector};
use crate::{BezierLineIter, BezierPointIter};
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Bezier<F, const D: usize>
where
F: Float,
{
num: usize,
pts: [[F; D]; 4],
}
impl<F, const D: usize> std::fmt::Display for Bezier<F, D>
where
F: Float,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "[")?;
vector::fmt(f, &self.pts[0])?;
write!(f, "<-")?;
if self.num > 2 {
vector::fmt(f, &self.pts[2])?;
}
if self.num > 3 {
write!(f, ":")?;
vector::fmt(f, &self.pts[3])?;
}
write!(f, "->")?;
vector::fmt(f, &self.pts[1])
}
}
impl<F, const D: usize> Bezier<F, D>
where
F: Float,
{
pub fn borrow_pt(&self, index: usize) -> &[F; D] {
&self.pts[index]
}
pub fn endpoints(self) -> ([F; D], [F; D]) {
(self.pts[0], self.pts[1])
}
pub fn get_distance(&self) -> F {
vector::distance(&self.pts[0], &self.pts[1])
}
pub fn line(p0: &[F; D], p1: &[F; D]) -> Self {
Self {
num: 2,
pts: [*p0, *p1, [F::zero(); D], [F::zero(); D]],
}
}
pub fn quadratic(p0: &[F; D], c: &[F; D], p1: &[F; D]) -> Self {
Self {
num: 3,
pts: [*p0, *p1, *c, [F::zero(); D]],
}
}
pub fn cubic(p0: &[F; D], c0: &[F; D], c1: &[F; D], p1: &[F; D]) -> Self {
Self {
num: 4,
pts: [*p0, *p1, *c0, *c1],
}
}
pub fn degree(&self) -> usize {
self.num - 1
}
pub fn scale(&mut self, s: F) {
self.map_pts(|p| vector::scale(p, s));
}
pub fn map_pts<Map: Fn([F; D]) -> [F; D]>(&mut self, map: Map) {
for p in self.pts.iter_mut() {
*p = map(*p);
}
}
#[inline]
fn vector_of(&self, sc: &[F], reduce: F) -> [F; D] {
let mut r = [F::zero(); D];
for (i, sc) in sc.iter().enumerate() {
for (j, rj) in r.iter_mut().enumerate() {
*rj += *sc * self.pts[i][j];
}
}
vector::reduce(r, reduce)
}
pub fn point_at(&self, t: F) -> [F; D] {
let two: F = (2.0_f32).into();
let three: F = (3.0_f32).into();
let omt = F::one() - t;
match self.num {
2 => self.vector_of(&[omt, t], F::one()),
3 => {
let p0_sc = omt * omt;
let c_sc = two * omt * t;
let p1_sc = t * t;
self.vector_of(&[p0_sc, p1_sc, c_sc], F::one())
}
_ => {
let p0_sc = omt * omt * omt;
let c0_sc = three * omt * omt * t;
let c1_sc = three * omt * t * t;
let p1_sc = t * t * t;
self.vector_of(&[p0_sc, p1_sc, c0_sc, c1_sc], F::one())
}
}
}
pub fn tangent_at(&self, t: F) -> [F; D] {
let one = F::one();
let two: F = (2.0_f32).into();
let three: F = (3.0_f32).into();
let four: F = (4.0_f32).into();
match self.num {
2 => self.vector_of(&[-one, one], one),
3 => {
let p0_sc = t - one; let c_sc = one - two * t; let p1_sc = t; self.vector_of(&[p0_sc, p1_sc, c_sc], one)
}
_ => {
let p0_sc = two * t - t * t - one; let c0_sc = three * t * t - four * t + one; let c1_sc = two * t - three * t * t; let p1_sc = t * t; self.vector_of(&[p0_sc, p1_sc, c0_sc, c1_sc], one)
}
}
}
pub fn bisect(&self) -> (Self, Self) {
let zero = F::zero();
let one = F::one();
let two = (2.0_f32).into();
let three: F = (3.0_f32).into();
let four: F = (4.0_f32).into();
let eight: F = (8.0_f32).into();
match self.num {
2 => {
let pm = self.vector_of(&[one, one], two);
(Self::line(&self.pts[0], &pm), Self::line(&pm, &self.pts[1]))
}
3 => {
let c0 = self.vector_of(&[one, zero, one], two);
let c1 = self.vector_of(&[zero, one, one], two);
let pm = vector::add(c0, &c1, F::one());
let pm = vector::reduce(pm, 2.0_f32.into());
(
Self::quadratic(&self.pts[0], &c0, &pm),
Self::quadratic(&pm, &c1, &self.pts[1]),
)
}
_ => {
let pm = self.vector_of(&[one, one, three, three], eight);
let c00 = self.vector_of(&[one, zero, one], two);
let c01 = self.vector_of(&[one, zero, two, one], four);
let c10 = self.vector_of(&[zero, one, one, two], four);
let c11 = self.vector_of(&[zero, one, zero, one], two);
(
Self::cubic(&self.pts[0], &c00, &c01, &pm),
Self::cubic(&pm, &c10, &c11, &self.pts[1]),
)
}
}
}
pub fn bezier_between(&self, t0: F, t1: F) -> Self
where
for<'a> &'a [F; D]: Into<FArray<F, D>>,
FArray<F, D>: Vector<F, D>,
{
match self.num {
2 => {
let u0 = F::one() - t0;
let u1 = F::one() - t1;
let p0: FArray<F, D> = self.pts[0].into();
let p1: FArray<F, D> = self.pts[1].into();
let r0 = p0 * u0 + p1 * t0;
let r1 = p0 * u1 + p1 * t1;
Self::line(&r0, &r1)
}
3 => {
let two: F = (2.0_f32).into();
let p0: FArray<F, D> = self.pts[0].into();
let p1: FArray<F, D> = self.pts[1].into();
let c: FArray<F, D> = self.pts[2].into();
let u0 = F::one() - t0;
let u1 = F::one() - t1;
let rp0 = p0 * (u0 * u0) + c * (two * u0 * t0) + p1 * (t0 * t0);
let rp1 = p0 * (u1 * u1) + c * (two * u1 * t1) + p1 * (t1 * t1);
let rc0 = p0 * (u0 * u1) + c * (u0 * t1 + u1 * t0) + p1 * (t1 * t0);
Self::quadratic(&rp0, &rc0, &rp1)
}
_ => {
let rp0: FArray<F, D> = self.point_at(t0).into();
let rt0: FArray<F, D> = self.tangent_at(t0).into();
let rt1: FArray<F, D> = self.tangent_at(t1).into();
let rp1: FArray<F, D> = self.point_at(t1).into();
let t1_m_t0 = t1 - t0;
let rc0 = rp0 + rt0 * t1_m_t0;
let rc1 = rp1 - rt1 * t1_m_t0;
Self::cubic(&rp0, &rc0, &rc1, &rp1)
}
}
}
pub fn as_lines(&self, straightness: F) -> BezierLineIter<F, D> {
BezierLineIter::new(self, straightness)
}
pub fn as_points(&self, straightness: F) -> BezierPointIter<F, D> {
BezierPointIter::new(BezierLineIter::new(self, straightness))
}
pub fn is_straight(&self, straightness: F) -> bool {
fn straightness_of_control<F, const D: usize>(p: &[F; D], lp2: F, c: &[F; D]) -> (F, F)
where
F: Float,
{
let lc2 = vector::length_sq(c);
if lc2 < F::epsilon() {
(F::zero(), lp2)
} else if lp2 < F::epsilon() {
(lc2, F::one())
} else {
let cdp = vector::dot(c, p);
let c_s = F::sqrt(lp2 * lc2 - cdp * cdp);
(c_s, lp2)
}
}
match self.num {
2 => true,
3 => {
let p = vector::sub(self.pts[1], &self.pts[0], F::one());
let c = vector::sub(self.pts[2], &self.pts[0], F::one());
let lp2 = vector::length_sq(&p);
let (c_s, sc) = straightness_of_control(&p, lp2, &c);
c_s <= straightness * sc
}
_ => {
let p = vector::sub(self.pts[1], &self.pts[0], F::one());
let c0 = vector::sub(self.pts[2], &self.pts[0], F::one());
let c1 = vector::sub(self.pts[3], &self.pts[0], F::one());
let lp2 = vector::length_sq(&p);
let (c0_s, sc0) = straightness_of_control(&p, lp2, &c0);
let (c1_s, sc1) = straightness_of_control(&p, lp2, &c1);
(c0_s + c1_s) <= straightness * F::max(sc0, sc1)
}
}
}
pub fn length(&self, straightness: F) -> F {
if self.is_straight(straightness) {
self.get_distance()
} else {
let (b0, b1) = self.bisect();
b0.length(straightness) + b1.length(straightness)
}
}
fn t_of_distance_rec(
&self,
straightness: F,
distance: F,
t_start: F,
t_scale: F,
acc_length: F,
) -> (Option<F>, F) {
let zero = F::zero();
let two = (2.0_f32).into();
if distance <= acc_length {
(Some(t_start), zero)
} else if self.is_straight(straightness) {
let d = self.get_distance();
if distance > acc_length + d {
(None, acc_length + d)
} else if d < F::epsilon() {
(Some(t_start + t_scale), acc_length + d)
} else {
let rel_d = distance - acc_length;
(Some(t_start + t_scale * rel_d / d), acc_length + d)
}
} else {
let t_subscale = t_scale / two;
let (b0, b1) = self.bisect();
match b0.t_of_distance_rec(straightness, distance, t_start, t_subscale, acc_length) {
(None, length) => b1.t_of_distance_rec(
straightness,
distance,
t_start + t_subscale,
t_subscale,
length,
),
r => r,
}
}
}
pub fn t_of_distance(&self, straightness: F, distance: F) -> (F, bool) {
let zero = F::zero();
let one = F::one();
if distance < zero {
(zero, false)
} else {
match self.t_of_distance_rec(straightness, distance, zero, one, zero) {
(None, _) => (one, false),
(Some(t), _) => (t, true),
}
}
}
fn lambda_of_k_d(k: F, d: F) -> F {
let k_d = k / d;
let k_d = k_d * k_d;
let a0: F = 3.160_323_6e-2_f32.into();
let a1: F = 2.795_054_2_f32.into();
let a2: F = (-1.148_674_3e1_f32).into();
let a3: F = 2.897_536_8e1_f32.into();
let a4: F = (-3.284_522_2e1_f32).into();
let a5: F = 1.377_942_9e1_f32.into();
a0 + a1 * k_d
+ a2 * k_d * k_d
+ a3 * k_d * k_d * k_d
+ a4 * k_d * k_d * k_d * k_d
+ a5 * k_d * k_d * k_d * k_d * k_d
}
pub fn arc(
angle: F,
radius: F,
center: &[F; D],
unit: &[F; D],
normal: &[F; D],
rotate: F,
) -> Self
where
for<'a> &'a [F; D]: Into<FArray<F, D>>,
FArray<F, D>: Vector<F, D>,
{
let two = (2.0_f32).into();
let half_angle = angle / two;
let s = half_angle.sin();
let lambda = radius * Self::lambda_of_k_d(s, F::one());
let d0a = rotate;
let (d0s, d0c) = d0a.sin_cos();
let d1a = rotate + angle;
let (d1s, d1c) = d1a.sin_cos();
let center: FArray<F, D> = center.into();
let unit: FArray<F, D> = unit.into();
let normal: FArray<F, D> = normal.into();
let p0 = center + unit * (d0c * radius) + normal * (d0s * radius);
let p1 = center + unit * (d1c * radius) + normal * (d1s * radius);
let c0 = p0 - unit * (d0s * lambda) + normal * (d0c * lambda);
let c1 = p1 + unit * (d1s * lambda) - normal * (d1c * lambda);
Self::cubic(p0.as_ref(), c0.as_ref(), c1.as_ref(), p1.as_ref())
}
pub fn of_round_corner(corner: &[F; D], v0: &[F; D], v1: &[F; D], radius: F) -> Self
where
for<'a> &'a [F; D]: Into<FArray<F, D>>,
FArray<F, D>: Vector<F, D>,
{
let nearly_one = (0.999_999_f32).into();
let one = F::one();
let two: F = (2.0_f32).into();
let corner: FArray<F, D> = corner.into();
let v0: FArray<F, D> = v0.into();
let v0 = v0.normalize();
let v1: FArray<F, D> = v1.into();
let v1 = v1.normalize();
let cos_alpha = v0.dot(&v1);
if cos_alpha.abs() >= nearly_one {
let p0 = corner - (v0 * radius);
let p1 = corner - (v1 * radius);
Self::quadratic(&p0, corner.as_ref(), &p1)
} else {
let r2 = radius * radius;
let d2 = two * r2 / (one - cos_alpha);
let k2 = d2 - r2;
let d = d2.sqrt();
let k = k2.sqrt();
let lambda = radius * Self::lambda_of_k_d(k, d);
let p0 = corner - (v0 * k);
let p1 = corner - (v1 * k);
let c0 = p0 + (v0 * lambda);
let c1 = p1 + (v1 * lambda);
Self::cubic(p0.as_ref(), c0.as_ref(), c1.as_ref(), p1.as_ref())
}
}
pub fn center_radius_of_bezier_arc(&self) -> ([F; D], F)
where
for<'a> &'a [F; D]: Into<FArray<F, D>>,
FArray<F, D>: Vector<F, D>,
{
let zero = F::zero();
let one = F::one();
let p0: FArray<F, D> = self.point_at(zero).into();
let p1: FArray<F, D> = self.point_at(one).into();
let t0: FArray<F, D> = self.tangent_at(zero).into();
let t1: FArray<F, D> = self.tangent_at(one).into();
let t0 = t0.normalize();
let t1 = t1.normalize();
let t1_d_t0 = t1.dot(&t0);
let p0_d_t0 = p0.dot(&t0);
let p1_d_t1 = p1.dot(&t1);
let k0 = (p0_d_t0 - p1_d_t1 * t1_d_t0) / (one - t1_d_t0 * t1_d_t0);
let k1 = (p1_d_t1 - p0_d_t0 * t1_d_t0) / (one - t1_d_t0 * t1_d_t0);
let c = t0 * k0 + t1 * k1;
let r = (c.distance(&p0) + c.distance(&p1)) / (2.0_f32).into();
(*c, r)
}
}