use std::cmp::Ordering;
use hyperreal::{Real, RealSign};
use crate::classify::{
classify_oriented_line, compare_reals, is_zero, orient2d_real_expr, real_sign,
};
use crate::{
Aabb2, Classification, CubicBezier2, CurveError, CurvePolicy, CurveResult, CurveString2,
LineSeg2, LineSide, Point2, QuadraticBezier2, Segment2, UncertaintyReason,
};
#[derive(Clone, Debug, PartialEq)]
pub struct BezierFlatteningOptions {
max_error: Real,
max_depth: usize,
}
impl BezierFlatteningOptions {
pub fn try_new(max_error: Real, max_depth: usize, policy: &CurvePolicy) -> CurveResult<Self> {
if max_depth == 0 {
return Err(CurveError::InvalidFlatteningOptions);
}
match real_sign(&max_error, policy) {
Some(RealSign::Positive) => Ok(Self {
max_error,
max_depth,
}),
Some(RealSign::Zero | RealSign::Negative) | None => {
Err(CurveError::InvalidFlatteningOptions)
}
}
}
pub const fn max_error(&self) -> &Real {
&self.max_error
}
pub const fn max_depth(&self) -> usize {
self.max_depth
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierFlatteningCertificate {
max_error: Real,
segment_count: usize,
max_depth: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BezierSimplificationErrorMetric {
ExactPolylineImageDistance,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BezierSimplificationBoundKind {
ProvenExact,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierSimplificationCertificate {
source_start: usize,
source_end: usize,
retained_vertex_count: usize,
removed_vertex_count: usize,
error_bound: Real,
source_flattening_error: Real,
source_flattening_max_depth: usize,
construction_policy: CurvePolicy,
metric: BezierSimplificationErrorMetric,
bound_kind: BezierSimplificationBoundKind,
}
impl BezierFlatteningCertificate {
pub const fn max_error(&self) -> &Real {
&self.max_error
}
pub const fn segment_count(&self) -> usize {
self.segment_count
}
pub const fn max_depth(&self) -> usize {
self.max_depth
}
}
impl BezierSimplificationCertificate {
fn proven_exact_collinear(
source_vertex_count: usize,
retained_vertex_count: usize,
source_flattening_error: Real,
source_flattening_max_depth: usize,
construction_policy: &CurvePolicy,
) -> Self {
Self {
source_start: 0,
source_end: source_vertex_count,
retained_vertex_count,
removed_vertex_count: source_vertex_count.saturating_sub(retained_vertex_count),
error_bound: Real::zero(),
source_flattening_error,
source_flattening_max_depth,
construction_policy: construction_policy.clone(),
metric: BezierSimplificationErrorMetric::ExactPolylineImageDistance,
bound_kind: BezierSimplificationBoundKind::ProvenExact,
}
}
pub const fn source_start(&self) -> usize {
self.source_start
}
pub const fn source_end(&self) -> usize {
self.source_end
}
pub const fn retained_vertex_count(&self) -> usize {
self.retained_vertex_count
}
pub const fn removed_vertex_count(&self) -> usize {
self.removed_vertex_count
}
pub const fn error_bound(&self) -> &Real {
&self.error_bound
}
pub const fn source_flattening_error(&self) -> &Real {
&self.source_flattening_error
}
pub const fn source_flattening_max_depth(&self) -> usize {
self.source_flattening_max_depth
}
pub const fn construction_policy(&self) -> &CurvePolicy {
&self.construction_policy
}
pub const fn metric(&self) -> BezierSimplificationErrorMetric {
self.metric
}
pub const fn bound_kind(&self) -> BezierSimplificationBoundKind {
self.bound_kind
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierPolyline2 {
points: Vec<Point2>,
certificate: BezierFlatteningCertificate,
simplification_certificate: Option<BezierSimplificationCertificate>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DisplayBezierOffsetPolyline2 {
segments: Vec<LineSeg2>,
source_certificate: BezierFlatteningCertificate,
distance: Real,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierPolylineOffset2 {
curve: CurveString2,
source_certificate: BezierFlatteningCertificate,
distance: Real,
}
impl DisplayBezierOffsetPolyline2 {
pub fn segments(&self) -> &[LineSeg2] {
&self.segments
}
pub const fn source_certificate(&self) -> &BezierFlatteningCertificate {
&self.source_certificate
}
pub const fn distance(&self) -> &Real {
&self.distance
}
}
impl CertifiedBezierPolylineOffset2 {
pub const fn curve(&self) -> &CurveString2 {
&self.curve
}
pub const fn source_certificate(&self) -> &BezierFlatteningCertificate {
&self.source_certificate
}
pub const fn distance(&self) -> &Real {
&self.distance
}
}
impl CertifiedBezierPolyline2 {
pub fn points(&self) -> &[Point2] {
&self.points
}
pub const fn certificate(&self) -> &BezierFlatteningCertificate {
&self.certificate
}
pub const fn simplification_certificate(&self) -> Option<&BezierSimplificationCertificate> {
self.simplification_certificate.as_ref()
}
pub fn simplify_exact_collinear(
&self,
policy: &CurvePolicy,
) -> Classification<CertifiedBezierPolyline2> {
if self.points.len() <= 2 {
return Classification::Decided(CertifiedBezierPolyline2 {
points: self.points.clone(),
certificate: self.certificate.clone(),
simplification_certificate: Some(
BezierSimplificationCertificate::proven_exact_collinear(
self.points.len(),
self.points.len(),
self.certificate.max_error.clone(),
self.certificate.max_depth,
policy,
),
),
});
}
let mut simplified = Vec::with_capacity(self.points.len());
simplified.push(self.points[0].clone());
for index in 1..self.points.len() - 1 {
let point = &self.points[index];
let previous = simplified
.last()
.expect("simplified always contains the start vertex");
let next = &self.points[index + 1];
match can_remove_collinear_vertex(previous, point, next, policy) {
Classification::Decided(true) => {}
Classification::Decided(false) => simplified.push(point.clone()),
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
}
}
simplified.push(
self.points
.last()
.expect("polyline contains at least two vertices")
.clone(),
);
let segment_count = simplified.len().saturating_sub(1);
Classification::Decided(CertifiedBezierPolyline2 {
points: simplified,
certificate: BezierFlatteningCertificate {
max_error: self.certificate.max_error.clone(),
segment_count,
max_depth: self.certificate.max_depth,
},
simplification_certificate: Some(
BezierSimplificationCertificate::proven_exact_collinear(
self.points.len(),
segment_count + 1,
self.certificate.max_error.clone(),
self.certificate.max_depth,
policy,
),
),
})
}
pub fn display_offset_left(&self, distance: Real) -> CurveResult<DisplayBezierOffsetPolyline2> {
let mut segments = Vec::new();
for window in self.points.windows(2) {
let segment = match LineSeg2::try_new(window[0].clone(), window[1].clone()) {
Ok(segment) => segment,
Err(CurveError::ZeroLengthLine) => continue,
Err(error) => return Err(error),
};
segments.push(segment.offset_left(distance.clone())?);
}
Ok(DisplayBezierOffsetPolyline2 {
segments,
source_certificate: self.certificate.clone(),
distance,
})
}
pub fn display_offset_right(
&self,
distance: Real,
) -> CurveResult<DisplayBezierOffsetPolyline2> {
self.display_offset_left(-distance)
}
pub fn checked_offset_left(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<CertifiedBezierPolylineOffset2>> {
let source = certified_polyline_curve_string(self)?;
match source.offset_left_checked(distance.clone(), policy)? {
Classification::Decided(curve) => {
Ok(Classification::Decided(CertifiedBezierPolylineOffset2 {
curve,
source_certificate: self.certificate.clone(),
distance,
}))
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
pub fn checked_offset_right(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<CertifiedBezierPolylineOffset2>> {
self.checked_offset_left(-distance, policy)
}
}
fn certified_polyline_curve_string(
polyline: &CertifiedBezierPolyline2,
) -> CurveResult<CurveString2> {
let mut segments = Vec::new();
for window in polyline.points.windows(2) {
match LineSeg2::try_new(window[0].clone(), window[1].clone()) {
Ok(segment) => segments.push(Segment2::Line(segment)),
Err(CurveError::ZeroLengthLine) => {}
Err(error) => return Err(error),
}
}
if segments.is_empty() {
return Err(CurveError::ZeroLengthLine);
}
CurveString2::try_new(segments)
}
fn can_remove_collinear_vertex(
previous: &Point2,
point: &Point2,
next: &Point2,
policy: &CurvePolicy,
) -> Classification<bool> {
match classify_oriented_line(previous, next, point, policy) {
Classification::Decided(LineSide::On) => {}
Classification::Decided(_) => return Classification::Decided(false),
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
}
let envelope = match Aabb2::from_points([previous, next], policy) {
Classification::Decided(bbox) => bbox,
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
};
envelope.contains_point(point, policy)
}
impl QuadraticBezier2 {
pub fn flatten_certified(
&self,
options: &BezierFlatteningOptions,
policy: &CurvePolicy,
) -> Classification<CertifiedBezierPolyline2> {
flatten_curve(self.clone(), options, policy)
}
}
impl CubicBezier2 {
pub fn flatten_certified(
&self,
options: &BezierFlatteningOptions,
policy: &CurvePolicy,
) -> Classification<CertifiedBezierPolyline2> {
flatten_curve(self.clone(), options, policy)
}
}
trait FlattenableBezier: Clone {
fn start(&self) -> &Point2;
fn end(&self) -> &Point2;
fn controls(&self) -> Vec<&Point2>;
fn split_half(&self) -> (Self, Self);
}
impl FlattenableBezier for QuadraticBezier2 {
fn start(&self) -> &Point2 {
self.start()
}
fn end(&self) -> &Point2 {
self.end()
}
fn controls(&self) -> Vec<&Point2> {
self.control_points().into_iter().collect()
}
fn split_half(&self) -> (Self, Self) {
let half = half();
let p01 = self.start().lerp(self.control(), half.clone());
let p12 = self.control().lerp(self.end(), half);
let mid = midpoint_point(&p01, &p12);
(
QuadraticBezier2::new(self.start().clone(), p01, mid.clone()),
QuadraticBezier2::new(mid, p12, self.end().clone()),
)
}
}
impl FlattenableBezier for CubicBezier2 {
fn start(&self) -> &Point2 {
self.start()
}
fn end(&self) -> &Point2 {
self.end()
}
fn controls(&self) -> Vec<&Point2> {
self.control_points().into_iter().collect()
}
fn split_half(&self) -> (Self, Self) {
let half = half();
let p01 = self.start().lerp(self.control1(), half.clone());
let p12 = self.control1().lerp(self.control2(), half.clone());
let p23 = self.control2().lerp(self.end(), half);
let p012 = midpoint_point(&p01, &p12);
let p123 = midpoint_point(&p12, &p23);
let mid = midpoint_point(&p012, &p123);
(
CubicBezier2::new(self.start().clone(), p01, p012, mid.clone()),
CubicBezier2::new(mid, p123, p23, self.end().clone()),
)
}
}
fn flatten_curve<C>(
curve: C,
options: &BezierFlatteningOptions,
policy: &CurvePolicy,
) -> Classification<CertifiedBezierPolyline2>
where
C: FlattenableBezier,
{
let mut points = vec![curve.start().clone()];
let max_error_squared = options.max_error() * options.max_error();
if let Err(reason) = flatten_recursive(
curve,
&max_error_squared,
options.max_depth(),
0,
policy,
&mut points,
) {
return Classification::Uncertain(reason);
}
let segment_count = points.len().saturating_sub(1);
Classification::Decided(CertifiedBezierPolyline2 {
points,
certificate: BezierFlatteningCertificate {
max_error: options.max_error().clone(),
segment_count,
max_depth: options.max_depth(),
},
simplification_certificate: None,
})
}
fn flatten_recursive<C>(
curve: C,
max_error_squared: &Real,
max_depth: usize,
depth: usize,
policy: &CurvePolicy,
points: &mut Vec<Point2>,
) -> Result<(), UncertaintyReason>
where
C: FlattenableBezier,
{
if curve_is_flat(&curve, max_error_squared, policy)? {
points.push(curve.end().clone());
return Ok(());
}
if depth >= max_depth {
return Err(UncertaintyReason::Unsupported);
}
let (left, right) = curve.split_half();
flatten_recursive(
left,
max_error_squared,
max_depth,
depth + 1,
policy,
points,
)?;
flatten_recursive(
right,
max_error_squared,
max_depth,
depth + 1,
policy,
points,
)
}
fn curve_is_flat<C>(
curve: &C,
max_error_squared: &Real,
policy: &CurvePolicy,
) -> Result<bool, UncertaintyReason>
where
C: FlattenableBezier,
{
if is_zero(&curve.start().distance_squared(curve.end()), policy) == Some(true) {
for point in curve.controls() {
if !squared_distance_within(point, curve.start(), max_error_squared, policy)? {
return Ok(false);
}
}
return Ok(true);
}
let chord_length_squared = curve.start().distance_squared(curve.end());
let threshold = max_error_squared * &chord_length_squared;
for point in curve.controls().into_iter().skip(1).rev().skip(1) {
let signed_area = orient2d_real_expr(curve.start(), curve.end(), point);
let area_squared = &signed_area * &signed_area;
match compare_reals(&area_squared, &threshold, policy) {
Some(Ordering::Less | Ordering::Equal) => {}
Some(Ordering::Greater) => return Ok(false),
None => return Err(UncertaintyReason::Ordering),
}
}
Ok(true)
}
fn squared_distance_within(
point: &Point2,
center: &Point2,
max_error_squared: &Real,
policy: &CurvePolicy,
) -> Result<bool, UncertaintyReason> {
match compare_reals(&point.distance_squared(center), max_error_squared, policy) {
Some(Ordering::Less | Ordering::Equal) => Ok(true),
Some(Ordering::Greater) => Ok(false),
None => Err(UncertaintyReason::Ordering),
}
}
fn midpoint_point(first: &Point2, second: &Point2) -> Point2 {
first.lerp(second, half())
}
fn half() -> Real {
(Real::one() / Real::from(2_i8)).expect("division by positive integer constant is defined")
}