use crate::classify::{classify_oriented_line, is_zero, real_sign};
use hyperreal::{Real, RealSign};
use crate::{
Aabb2, BezierFlatteningCertificate, CertifiedBezierPolyline2, Classification, CubicBezier2,
CurveError, CurvePolicy, CurveResult, LineSeg2, LineSide, Point2, QuadraticBezier2,
RationalQuadraticBezier2, UncertaintyReason,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BezierFitErrorMetric {
ExactEuclideanDistance,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BezierFitBoundKind {
ProvenExact,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierFitCertificate {
source_start: usize,
source_end: usize,
fit_error_bound: Real,
source_flattening_error: Option<Real>,
source_flattening_max_depth: Option<usize>,
construction_policy: CurvePolicy,
metric: BezierFitErrorMetric,
bound_kind: BezierFitBoundKind,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierPointImage2 {
point: Point2,
control_point_count: usize,
fit_certificate: BezierFitCertificate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierLineImage2 {
line: LineSeg2,
control_point_count: usize,
fit_certificate: BezierFitCertificate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierLineImageOffset2 {
line: LineSeg2,
control_point_count: usize,
distance: Real,
fit_certificate: BezierFitCertificate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierLineFit2 {
line: LineSeg2,
source_certificate: BezierFlatteningCertificate,
fit_certificate: BezierFitCertificate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierPointFit2 {
point: Point2,
source_certificate: BezierFlatteningCertificate,
fit_certificate: BezierFitCertificate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CertifiedBezierLineOffset2 {
line: LineSeg2,
source_certificate: BezierFlatteningCertificate,
distance: Real,
fit_certificate: BezierFitCertificate,
}
impl CertifiedBezierLineOffset2 {
pub const fn line(&self) -> &LineSeg2 {
&self.line
}
pub const fn source_certificate(&self) -> &BezierFlatteningCertificate {
&self.source_certificate
}
pub const fn distance(&self) -> &Real {
&self.distance
}
pub const fn fit_certificate(&self) -> &BezierFitCertificate {
&self.fit_certificate
}
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum BezierLineFitRelation {
Fit(CertifiedBezierLineFit2),
NotLine,
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum BezierPointFitRelation {
Fit(CertifiedBezierPointFit2),
NotPoint,
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum BezierLineImageFitRelation {
Fit(CertifiedBezierLineImage2),
NotLine,
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum BezierPointImageFitRelation {
Fit(CertifiedBezierPointImage2),
NotPoint,
}
impl BezierFitCertificate {
fn proven_exact(
source_start: usize,
source_end: usize,
source_flattening_error: Option<Real>,
source_flattening_max_depth: Option<usize>,
construction_policy: &CurvePolicy,
) -> Self {
Self {
source_start,
source_end,
fit_error_bound: Real::zero(),
source_flattening_error,
source_flattening_max_depth,
construction_policy: construction_policy.clone(),
metric: BezierFitErrorMetric::ExactEuclideanDistance,
bound_kind: BezierFitBoundKind::ProvenExact,
}
}
pub const fn source_start(&self) -> usize {
self.source_start
}
pub const fn source_end(&self) -> usize {
self.source_end
}
pub const fn fit_error_bound(&self) -> &Real {
&self.fit_error_bound
}
pub const fn source_flattening_error(&self) -> Option<&Real> {
self.source_flattening_error.as_ref()
}
pub const fn source_flattening_max_depth(&self) -> Option<usize> {
self.source_flattening_max_depth
}
pub const fn construction_policy(&self) -> &CurvePolicy {
&self.construction_policy
}
pub const fn metric(&self) -> BezierFitErrorMetric {
self.metric
}
pub const fn bound_kind(&self) -> BezierFitBoundKind {
self.bound_kind
}
}
impl CertifiedBezierPointImage2 {
pub const fn point(&self) -> &Point2 {
&self.point
}
pub const fn control_point_count(&self) -> usize {
self.control_point_count
}
pub const fn fit_certificate(&self) -> &BezierFitCertificate {
&self.fit_certificate
}
}
impl CertifiedBezierLineImage2 {
pub const fn line(&self) -> &LineSeg2 {
&self.line
}
pub const fn control_point_count(&self) -> usize {
self.control_point_count
}
pub const fn fit_certificate(&self) -> &BezierFitCertificate {
&self.fit_certificate
}
pub fn offset_left_exact(
&self,
distance: Real,
) -> CurveResult<CertifiedBezierLineImageOffset2> {
Ok(CertifiedBezierLineImageOffset2 {
line: self.line.offset_left(distance.clone())?,
control_point_count: self.control_point_count,
distance,
fit_certificate: self.fit_certificate.clone(),
})
}
pub fn offset_right_exact(
&self,
distance: Real,
) -> CurveResult<CertifiedBezierLineImageOffset2> {
self.offset_left_exact(-distance)
}
}
impl CertifiedBezierLineImageOffset2 {
pub const fn line(&self) -> &LineSeg2 {
&self.line
}
pub const fn control_point_count(&self) -> usize {
self.control_point_count
}
pub const fn distance(&self) -> &Real {
&self.distance
}
pub const fn fit_certificate(&self) -> &BezierFitCertificate {
&self.fit_certificate
}
}
impl CertifiedBezierLineFit2 {
pub const fn line(&self) -> &LineSeg2 {
&self.line
}
pub const fn source_certificate(&self) -> &BezierFlatteningCertificate {
&self.source_certificate
}
pub const fn fit_certificate(&self) -> &BezierFitCertificate {
&self.fit_certificate
}
pub fn offset_left_exact(&self, distance: Real) -> CurveResult<CertifiedBezierLineOffset2> {
Ok(CertifiedBezierLineOffset2 {
line: self.line.offset_left(distance.clone())?,
source_certificate: self.source_certificate.clone(),
distance,
fit_certificate: self.fit_certificate.clone(),
})
}
pub fn offset_right_exact(&self, distance: Real) -> CurveResult<CertifiedBezierLineOffset2> {
self.offset_left_exact(-distance)
}
}
impl CertifiedBezierPointFit2 {
pub const fn point(&self) -> &Point2 {
&self.point
}
pub const fn source_certificate(&self) -> &BezierFlatteningCertificate {
&self.source_certificate
}
pub const fn fit_certificate(&self) -> &BezierFitCertificate {
&self.fit_certificate
}
}
impl QuadraticBezier2 {
pub fn fit_exact_point_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierPointImageFitRelation>> {
fit_control_polygon_point_image(&self.control_points(), policy)
}
pub fn fit_exact_line_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
fit_control_polygon_line_image(&self.control_points(), policy)
}
}
impl CubicBezier2 {
pub fn fit_exact_point_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierPointImageFitRelation>> {
fit_control_polygon_point_image(&self.control_points(), policy)
}
pub fn fit_exact_line_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
fit_control_polygon_line_image(&self.control_points(), policy)
}
}
impl RationalQuadraticBezier2 {
pub fn fit_exact_point_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierPointImageFitRelation>> {
match weights_known_same_nonzero_sign(self.weights().as_slice(), policy) {
Some(true) => fit_control_polygon_point_image(&self.control_points(), policy),
Some(false) => Ok(Classification::Uncertain(UncertaintyReason::Unsupported)),
None => Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
pub fn fit_exact_line_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
match weights_known_same_nonzero_sign(self.weights().as_slice(), policy) {
Some(true) => fit_control_polygon_line_image(&self.control_points(), policy),
Some(false) => Ok(Classification::Uncertain(UncertaintyReason::Unsupported)),
None => Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
}
impl CertifiedBezierPolyline2 {
pub fn fit_exact_point(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierPointFitRelation>> {
let Some(point) = self.points().first() else {
return Err(CurveError::InsufficientVertices);
};
for vertex in self.points().iter().skip(1) {
match is_zero(&point.distance_squared(vertex), policy) {
Some(true) => {}
Some(false) => {
return Ok(Classification::Decided(BezierPointFitRelation::NotPoint));
}
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
Ok(Classification::Decided(BezierPointFitRelation::Fit(
CertifiedBezierPointFit2 {
point: point.clone(),
source_certificate: self.certificate().clone(),
fit_certificate: BezierFitCertificate::proven_exact(
0,
self.points().len(),
Some(self.certificate().max_error().clone()),
Some(self.certificate().max_depth()),
policy,
),
},
)))
}
pub fn fit_exact_line(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineFitRelation>> {
let Some(start) = self.points().first() else {
return Err(CurveError::InsufficientVertices);
};
let Some(end) = self.points().last() else {
return Err(CurveError::InsufficientVertices);
};
if is_zero(&start.distance_squared(end), policy) == Some(true) {
return Err(CurveError::ZeroLengthLine);
}
let line = LineSeg2::try_new(start.clone(), end.clone())?;
let envelope = match Aabb2::from_points([start, end], policy) {
Classification::Decided(envelope) => envelope,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
for point in self
.points()
.iter()
.skip(1)
.take(self.points().len().saturating_sub(2))
{
match point_on_line_interval(start, end, &envelope, point, policy) {
Classification::Decided(true) => {}
Classification::Decided(false) => {
return Ok(Classification::Decided(BezierLineFitRelation::NotLine));
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
Ok(Classification::Decided(BezierLineFitRelation::Fit(
CertifiedBezierLineFit2 {
line,
source_certificate: self.certificate().clone(),
fit_certificate: BezierFitCertificate::proven_exact(
0,
self.points().len(),
Some(self.certificate().max_error().clone()),
Some(self.certificate().max_depth()),
policy,
),
},
)))
}
}
fn weights_known_same_nonzero_sign(weights: &[&Real], policy: &CurvePolicy) -> Option<bool> {
let mut expected = None;
for weight in weights {
let sign = real_sign(weight, policy)?;
match sign {
RealSign::Positive | RealSign::Negative => {
if let Some(expected) = expected {
if sign != expected {
return Some(false);
}
} else {
expected = Some(sign);
}
}
RealSign::Zero => return Some(false),
}
}
Some(expected.is_some())
}
fn fit_control_polygon_point_image(
controls: &[&Point2],
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierPointImageFitRelation>> {
let Some(point) = controls.first().copied() else {
return Err(CurveError::InsufficientVertices);
};
for control in controls.iter().skip(1) {
match is_zero(&point.distance_squared(control), policy) {
Some(true) => {}
Some(false) => {
return Ok(Classification::Decided(
BezierPointImageFitRelation::NotPoint,
));
}
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
Ok(Classification::Decided(BezierPointImageFitRelation::Fit(
CertifiedBezierPointImage2 {
point: point.clone(),
control_point_count: controls.len(),
fit_certificate: BezierFitCertificate::proven_exact(
0,
controls.len(),
None,
None,
policy,
),
},
)))
}
fn fit_control_polygon_line_image(
controls: &[&Point2],
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
let Some(start) = controls.first().copied() else {
return Err(CurveError::InsufficientVertices);
};
let Some(end) = controls.last().copied() else {
return Err(CurveError::InsufficientVertices);
};
if is_zero(&start.distance_squared(end), policy) == Some(true) {
return Err(CurveError::ZeroLengthLine);
}
let line = LineSeg2::try_new(start.clone(), end.clone())?;
let envelope = match Aabb2::from_points([start, end], policy) {
Classification::Decided(envelope) => envelope,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
for point in controls
.iter()
.skip(1)
.take(controls.len().saturating_sub(2))
{
match point_on_line_interval(start, end, &envelope, point, policy) {
Classification::Decided(true) => {}
Classification::Decided(false) => {
return Ok(Classification::Decided(BezierLineImageFitRelation::NotLine));
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
Ok(Classification::Decided(BezierLineImageFitRelation::Fit(
CertifiedBezierLineImage2 {
line,
control_point_count: controls.len(),
fit_certificate: BezierFitCertificate::proven_exact(
0,
controls.len(),
None,
None,
policy,
),
},
)))
}
fn point_on_line_interval(
start: &Point2,
end: &Point2,
envelope: &Aabb2,
point: &Point2,
policy: &CurvePolicy,
) -> Classification<bool> {
match classify_oriented_line(start, end, point, policy) {
Classification::Decided(LineSide::On) => {}
Classification::Decided(_) => return Classification::Decided(false),
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
}
envelope.contains_point(point, policy)
}