use std::ops::Range;
use hyperreal::Real;
use crate::classify::in_closed_unit_interval;
use crate::{
Classification, CubicBezier2, CurveError, CurvePolicy, CurveResult, Point2, QuadraticBezier2,
UncertaintyReason,
};
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAreaMoments2 {
signed_area: Real,
x_moment: Real,
y_moment: Real,
}
impl BezierAreaMoments2 {
pub fn zero() -> Self {
Self {
signed_area: Real::zero(),
x_moment: Real::zero(),
y_moment: Real::zero(),
}
}
pub fn signed_area(&self) -> &Real {
&self.signed_area
}
pub fn x_moment(&self) -> &Real {
&self.x_moment
}
pub fn y_moment(&self) -> &Real {
&self.y_moment
}
fn plus(&self, other: &Self) -> Self {
Self {
signed_area: &self.signed_area + &other.signed_area,
x_moment: &self.x_moment + &other.x_moment,
y_moment: &self.y_moment + &other.y_moment,
}
}
fn minus(&self, other: &Self) -> Self {
Self {
signed_area: &self.signed_area - &other.signed_area,
x_moment: &self.x_moment - &other.x_moment,
y_moment: &self.y_moment - &other.y_moment,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAreaMomentPrefixSums2 {
prefixes: Vec<BezierAreaMoments2>,
}
impl BezierAreaMomentPrefixSums2 {
pub fn from_contributions(contributions: impl IntoIterator<Item = BezierAreaMoments2>) -> Self {
let mut prefixes = vec![BezierAreaMoments2::zero()];
for contribution in contributions {
let next = prefixes
.last()
.expect("prefix list always contains zero")
.plus(&contribution);
prefixes.push(next);
}
Self { prefixes }
}
pub fn from_quadratics<'a>(
curves: impl IntoIterator<Item = &'a QuadraticBezier2>,
) -> CurveResult<Self> {
curves
.into_iter()
.map(QuadraticBezier2::area_moments_contribution)
.collect::<CurveResult<Vec<_>>>()
.map(Self::from_contributions)
}
pub fn from_cubics<'a>(
curves: impl IntoIterator<Item = &'a CubicBezier2>,
) -> CurveResult<Self> {
curves
.into_iter()
.map(CubicBezier2::area_moments_contribution)
.collect::<CurveResult<Vec<_>>>()
.map(Self::from_contributions)
}
pub fn segment_count(&self) -> usize {
self.prefixes.len().saturating_sub(1)
}
pub fn total(&self) -> &BezierAreaMoments2 {
self.prefixes
.last()
.expect("prefix list always contains zero")
}
pub fn prefixes(&self) -> &[BezierAreaMoments2] {
&self.prefixes
}
pub fn range_contribution(&self, range: Range<usize>) -> CurveResult<BezierAreaMoments2> {
if range.start > range.end || range.end > self.segment_count() {
return Err(CurveError::InvalidBezierRange);
}
Ok(self.prefixes[range.end].minus(&self.prefixes[range.start]))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAreaPrefixSums2 {
prefixes: Vec<Real>,
}
impl BezierAreaPrefixSums2 {
pub fn from_contributions(contributions: impl IntoIterator<Item = Real>) -> Self {
let mut prefixes = vec![Real::zero()];
for contribution in contributions {
let next = prefixes.last().expect("prefix list always contains zero") + &contribution;
prefixes.push(next);
}
Self { prefixes }
}
pub fn from_quadratics<'a>(
curves: impl IntoIterator<Item = &'a QuadraticBezier2>,
) -> CurveResult<Self> {
curves
.into_iter()
.map(QuadraticBezier2::signed_area_contribution)
.collect::<CurveResult<Vec<_>>>()
.map(Self::from_contributions)
}
pub fn from_cubics<'a>(
curves: impl IntoIterator<Item = &'a CubicBezier2>,
) -> CurveResult<Self> {
curves
.into_iter()
.map(CubicBezier2::signed_area_contribution)
.collect::<CurveResult<Vec<_>>>()
.map(Self::from_contributions)
}
pub fn segment_count(&self) -> usize {
self.prefixes.len().saturating_sub(1)
}
pub fn total(&self) -> &Real {
self.prefixes
.last()
.expect("prefix list always contains zero")
}
pub fn prefixes(&self) -> &[Real] {
&self.prefixes
}
pub fn range_contribution(&self, range: Range<usize>) -> CurveResult<Real> {
if range.start > range.end || range.end > self.segment_count() {
return Err(CurveError::InvalidBezierRange);
}
Ok(&self.prefixes[range.end] - &self.prefixes[range.start])
}
}
impl QuadraticBezier2 {
pub fn signed_area_contribution(&self) -> CurveResult<Real> {
Ok(area_moments_for_controls(&self.control_points())?.signed_area)
}
pub fn area_moments_contribution(&self) -> CurveResult<BezierAreaMoments2> {
area_moments_for_controls(&self.control_points())
}
pub fn prefix_signed_area_contribution(
&self,
t: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Real>> {
prefix_signed_area_for_controls(
self.control_points().into_iter().cloned().collect(),
t,
policy,
)
}
pub fn prefix_area_moments_contribution(
&self,
t: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierAreaMoments2>> {
prefix_area_moments_for_controls(
self.control_points().into_iter().cloned().collect(),
t,
policy,
)
}
}
impl CubicBezier2 {
pub fn signed_area_contribution(&self) -> CurveResult<Real> {
Ok(area_moments_for_controls(&self.control_points())?.signed_area)
}
pub fn area_moments_contribution(&self) -> CurveResult<BezierAreaMoments2> {
area_moments_for_controls(&self.control_points())
}
pub fn prefix_signed_area_contribution(
&self,
t: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Real>> {
prefix_signed_area_for_controls(
self.control_points().into_iter().cloned().collect(),
t,
policy,
)
}
pub fn prefix_area_moments_contribution(
&self,
t: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierAreaMoments2>> {
prefix_area_moments_for_controls(
self.control_points().into_iter().cloned().collect(),
t,
policy,
)
}
}
fn prefix_signed_area_for_controls(
controls: Vec<Point2>,
t: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Real>> {
match in_closed_unit_interval(&t, policy) {
Some(true) => {}
Some(false) => return Err(CurveError::InvalidBezierParameter),
None => return Ok(Classification::Uncertain(UncertaintyReason::Ordering)),
}
let (prefix, _) = subdivide_controls_at(&controls, t);
let refs = prefix.iter().collect::<Vec<_>>();
area_moments_for_controls(&refs).map(|moments| Classification::Decided(moments.signed_area))
}
fn prefix_area_moments_for_controls(
controls: Vec<Point2>,
t: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierAreaMoments2>> {
match in_closed_unit_interval(&t, policy) {
Some(true) => {}
Some(false) => return Err(CurveError::InvalidBezierParameter),
None => return Ok(Classification::Uncertain(UncertaintyReason::Ordering)),
}
let (prefix, _) = subdivide_controls_at(&controls, t);
let refs = prefix.iter().collect::<Vec<_>>();
area_moments_for_controls(&refs).map(Classification::Decided)
}
fn area_moments_for_controls(controls: &[&Point2]) -> CurveResult<BezierAreaMoments2> {
let x = bernstein_to_power(
controls
.iter()
.map(|point| point.x().clone())
.collect::<Vec<_>>(),
);
let y = bernstein_to_power(
controls
.iter()
.map(|point| point.y().clone())
.collect::<Vec<_>>(),
);
let dx = derivative_coefficients(&x);
let dy = derivative_coefficients(&y);
let first = polynomial_product(&x, &dy);
let second = polynomial_product(&y, &dx);
let signed_area_integral = integrate_polynomial_difference(&first, &second)?;
let x_squared = polynomial_product(&x, &x);
let y_squared = polynomial_product(&y, &y);
let x_moment_integral = integrate_polynomial(&polynomial_product(&x_squared, &dy))?;
let y_moment_integral = integrate_polynomial(&polynomial_product(&y_squared, &dx))?;
Ok(BezierAreaMoments2 {
signed_area: (signed_area_integral / Real::from(2_i8))?,
x_moment: (x_moment_integral / Real::from(2_i8))?,
y_moment: (Real::zero() - (y_moment_integral / Real::from(2_i8))?),
})
}
fn integrate_polynomial_difference(first: &[Real], second: &[Real]) -> CurveResult<Real> {
let mut integral = Real::zero();
for degree in 0..first.len().max(second.len()) {
let value = first.get(degree).cloned().unwrap_or_else(Real::zero)
- second.get(degree).cloned().unwrap_or_else(Real::zero);
integral = &integral + (value / Real::from((degree + 1) as i32))?;
}
Ok(integral)
}
fn integrate_polynomial(coefficients: &[Real]) -> CurveResult<Real> {
let mut integral = Real::zero();
for (degree, coefficient) in coefficients.iter().enumerate() {
integral = &integral + (coefficient.clone() / Real::from((degree + 1) as i32))?;
}
Ok(integral)
}
fn bernstein_to_power(values: Vec<Real>) -> Vec<Real> {
let degree = values.len() - 1;
let mut coeffs = vec![Real::zero(); values.len()];
for (i, value) in values.into_iter().enumerate() {
for (k, coefficient) in coeffs.iter_mut().enumerate().take(degree + 1).skip(i) {
let magnitude = binomial(degree, i) * binomial(degree - i, k - i);
let signed = if (k - i) % 2 == 0 {
magnitude as i32
} else {
-(magnitude as i32)
};
*coefficient = &*coefficient + (&value * &Real::from(signed));
}
}
coeffs
}
fn derivative_coefficients(coefficients: &[Real]) -> Vec<Real> {
coefficients
.iter()
.enumerate()
.skip(1)
.map(|(degree, coefficient)| coefficient * &Real::from(degree as i32))
.collect()
}
fn polynomial_product(first: &[Real], second: &[Real]) -> Vec<Real> {
if first.is_empty() || second.is_empty() {
return Vec::new();
}
let mut product = vec![Real::zero(); first.len() + second.len() - 1];
for (i, a) in first.iter().enumerate() {
for (j, b) in second.iter().enumerate() {
product[i + j] = &product[i + j] + &(a * b);
}
}
product
}
fn subdivide_controls_at(controls: &[Point2], t: Real) -> (Vec<Point2>, Vec<Point2>) {
let mut levels = vec![controls.to_vec()];
while levels.last().map(|level| level.len()).unwrap_or(0) > 1 {
let previous = levels.last().expect("level exists");
let next = previous
.windows(2)
.map(|pair| pair[0].lerp(&pair[1], t.clone()))
.collect::<Vec<_>>();
levels.push(next);
}
let left = levels
.iter()
.map(|level| level[0].clone())
.collect::<Vec<_>>();
let right = levels
.iter()
.rev()
.map(|level| level[level.len() - 1].clone())
.collect::<Vec<_>>();
(left, right)
}
fn binomial(n: usize, k: usize) -> usize {
match (n, k) {
(_, 0) => 1,
(n, k) if n == k => 1,
(2, 1) => 2,
(3, 1 | 2) => 3,
_ => unreachable!("Bezier moment support is currently quadratic/cubic"),
}
}