Files
OneCAD/crates/geom/src/blend.rs
T

54 lines
2.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Blend curves: one Bézier that joins two curves with a requested continuity order, by Hermite
//! interpolation of each reference curve's position and derivatives at the joints. This is the
//! geometry `sketch.blend_curve` builds; it knows nothing about sketches or the document.
use crate::{MAX_DERIVATIVE_ORDER, Vec2, profile::binom};
/// Control points of the Bézier of degree `2k + 1`, `k = d0.len()`, that starts at `p0` with
/// derivatives `d0` and ends at `p1` with derivatives `d1`, all with respect to its own parameter
/// on [0, 1]. The derivative vectors are the reference curves' at the joints, measured in their
/// own parameters and oriented the way the blend runs through the joint, so the blend agrees with
/// each reference through its k-th derivative (G1, G2, G3, G4 for k = 1…4).
///
/// The two ends fix exactly half of the controls each: a Bézier's j-th forward difference is
/// `P⁽ʲ⁾(0)·(n − j)!/n!`, so the first `k + 1` controls follow from the start data and the last
/// `k + 1` from the end data, which meet exactly at the middle control. `None` when the ends
/// disagree on the order, the order is above [`MAX_DERIVATIVE_ORDER`], or a value is not finite.
pub fn hermite_bezier(p0: Vec2, p1: Vec2, d0: &[Vec2], d1: &[Vec2]) -> Option<Vec<Vec2>> {
let k = d0.len();
if k != d1.len() || k > MAX_DERIVATIVE_ORDER || !p0.is_finite() || !p1.is_finite() || d0.iter().chain(d1).any(|d| !d.is_finite()) {
return None;
}
let n = 2 * k + 1;
let mut c = vec![Vec2::ZERO; n + 1];
c[0] = p0;
for j in 1..=k {
// `Δʲp₀ = P⁽ʲ⁾(0)·(n − j)!/n!`, and solving it for `pⱼ` puts `(−1)^(j − i + 1)` on every
// control already known.
let scale = 1.0 / (0..j).fold(1.0, |acc, i| acc * (n - i) as f64);
let mut value = d0[j - 1] * scale;
for (i, p) in c.iter().take(j).enumerate() {
value += *p * (odd_sign(j - i) * binom(j, i));
}
c[j] = value;
}
c[n] = p1;
for j in 1..=k {
// The same at the far end, reading the controls backwards: `Σᵢ(−1)ⁱ·C(j,i)·pₙ₋ᵢ` is
// `P⁽ʲ⁾(1)·(n − j)!/n!`, and solving that for `pₙ₋ⱼ` puts `(−1)^(j + i + 1)` on every control
// already known.
let scale = 1.0 / (0..j).fold(1.0, |acc, i| acc * (n - i) as f64);
let mut value = d1[j - 1] * (scale * odd_sign(j + 1));
for (i, p) in c[n - j + 1..].iter().rev().enumerate() {
value += *p * (odd_sign(j + i) * binom(j, i));
}
c[n - j] = value;
}
c.iter().all(|p| p.is_finite()).then_some(c)
}
/// `(−1)^(m + 1)`: the sign an m-th difference expansion carries in a Bézier's endpoint identity.
fn odd_sign(m: usize) -> f64 {
if m % 2 == 1 { 1.0 } else { -1.0 }
}