use hyperreal::{RealSign, ZeroKnowledge as ZeroStatus};
use crate::classify::real_sign;
use crate::{
BezierCuspClassification, BezierDegree, BezierEndpoint, BezierInflectionClassification,
BezierLineImageFitRelation, CertifiedBezierLineImageOffset2, Classification, CubicBezier2,
CurveError, CurvePolicy, CurveResult, Point2, QuadraticBezier2, RationalQuadraticBezier2, Real,
UncertaintyReason,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BezierOffsetRisk {
DegeneratePoint,
Cusp,
Inflection,
AllCurvatureZero,
UndefinedEndpointNormal {
endpoint: BezierEndpoint,
},
UnresolvedEndpointNormal {
endpoint: BezierEndpoint,
},
CoincidentEndpoints,
ProjectiveDenominatorBoundary,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierOffsetPreflight2 {
degree: BezierDegree,
cusp_classification: BezierCuspClassification,
inflection_classification: BezierInflectionClassification,
start_tangent_status: ZeroStatus,
end_tangent_status: ZeroStatus,
endpoint_coincidence: ZeroStatus,
risks: Vec<BezierOffsetRisk>,
construction_policy: CurvePolicy,
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum BezierOffsetCandidate2 {
ExactLineImage {
offset: CertifiedBezierLineImageOffset2,
preflight: BezierOffsetPreflight2,
},
Unresolved {
preflight: BezierOffsetPreflight2,
distance: Real,
},
}
impl BezierOffsetPreflight2 {
pub const fn degree(&self) -> BezierDegree {
self.degree
}
pub const fn cusp_classification(&self) -> &BezierCuspClassification {
&self.cusp_classification
}
pub const fn inflection_classification(&self) -> &BezierInflectionClassification {
&self.inflection_classification
}
pub const fn start_tangent_status(&self) -> ZeroStatus {
self.start_tangent_status
}
pub const fn end_tangent_status(&self) -> ZeroStatus {
self.end_tangent_status
}
pub const fn endpoint_coincidence(&self) -> ZeroStatus {
self.endpoint_coincidence
}
pub fn risks(&self) -> &[BezierOffsetRisk] {
&self.risks
}
pub fn is_clear(&self) -> bool {
self.risks.is_empty()
}
pub const fn construction_policy(&self) -> &CurvePolicy {
&self.construction_policy
}
}
impl BezierOffsetCandidate2 {
pub const fn preflight(&self) -> &BezierOffsetPreflight2 {
match self {
Self::ExactLineImage { preflight, .. } | Self::Unresolved { preflight, .. } => {
preflight
}
}
}
pub const fn exact_line_image_offset(&self) -> Option<&CertifiedBezierLineImageOffset2> {
match self {
Self::ExactLineImage { offset, .. } => Some(offset),
Self::Unresolved { .. } => None,
}
}
pub const fn unresolved_preflight(&self) -> Option<&BezierOffsetPreflight2> {
match self {
Self::ExactLineImage { .. } => None,
Self::Unresolved { preflight, .. } => Some(preflight),
}
}
pub const fn distance(&self) -> &Real {
match self {
Self::ExactLineImage { offset, .. } => offset.distance(),
Self::Unresolved { distance, .. } => distance,
}
}
}
impl QuadraticBezier2 {
pub fn offset_preflight(&self, policy: &CurvePolicy) -> Classification<BezierOffsetPreflight2> {
let cusp_classification = match self.cusp_classification(policy) {
Classification::Decided(classification) => classification,
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
};
let inflection_classification = self.inflection_classification();
let start_tangent_status = self.endpoint_tangent(BezierEndpoint::Start).zero_status();
let end_tangent_status = self.endpoint_tangent(BezierEndpoint::End).zero_status();
let endpoint_coincidence = self.endpoints_coincident_status();
Classification::Decided(build_preflight(
BezierDegree::Quadratic,
cusp_classification,
inflection_classification,
start_tangent_status,
end_tangent_status,
endpoint_coincidence,
policy,
))
}
pub fn offset_left_staged(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierOffsetCandidate2>> {
staged_offset_left(self, distance, policy)
}
pub fn offset_right_staged(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierOffsetCandidate2>> {
staged_offset_left(self, -distance, policy)
}
}
impl CubicBezier2 {
pub fn offset_preflight(&self, policy: &CurvePolicy) -> Classification<BezierOffsetPreflight2> {
let cusp_classification = match self.cusp_classification(policy) {
Classification::Decided(classification) => classification,
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
};
let inflection_classification = match self.inflection_classification(policy) {
Classification::Decided(classification) => classification,
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
};
let start_tangent_status = self.endpoint_tangent(BezierEndpoint::Start).zero_status();
let end_tangent_status = self.endpoint_tangent(BezierEndpoint::End).zero_status();
let endpoint_coincidence = self.endpoints_coincident_status();
Classification::Decided(build_preflight(
BezierDegree::Cubic,
cusp_classification,
inflection_classification,
start_tangent_status,
end_tangent_status,
endpoint_coincidence,
policy,
))
}
pub fn offset_left_staged(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierOffsetCandidate2>> {
staged_offset_left(self, distance, policy)
}
pub fn offset_right_staged(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierOffsetCandidate2>> {
staged_offset_left(self, -distance, policy)
}
}
impl RationalQuadraticBezier2 {
pub fn offset_preflight(&self, policy: &CurvePolicy) -> Classification<BezierOffsetPreflight2> {
let denominator_risk =
match weights_known_same_nonzero_sign(self.weights().as_slice(), policy) {
Some(true) => false,
Some(false) => true,
None => return Classification::Uncertain(UncertaintyReason::RealSign),
};
let start_tangent_status = rational_endpoint_delta_status(self.start(), self.control());
let end_tangent_status = rational_endpoint_delta_status(self.control(), self.end());
let endpoint_coincidence = self.start().distance_squared(self.end()).zero_status();
let mut preflight = build_preflight(
BezierDegree::Quadratic,
BezierCuspClassification::None,
BezierInflectionClassification::NotApplicable,
start_tangent_status,
end_tangent_status,
endpoint_coincidence,
policy,
);
if denominator_risk {
preflight
.risks
.push(BezierOffsetRisk::ProjectiveDenominatorBoundary);
}
if rational_collapsed_point_status(self) == ZeroStatus::Zero
&& !preflight.risks.contains(&BezierOffsetRisk::DegeneratePoint)
{
preflight.risks.insert(0, BezierOffsetRisk::DegeneratePoint);
}
Classification::Decided(preflight)
}
pub fn offset_left_staged(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierOffsetCandidate2>> {
staged_offset_left(self, distance, policy)
}
pub fn offset_right_staged(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierOffsetCandidate2>> {
staged_offset_left(self, -distance, policy)
}
}
trait StagedBezierOffset {
fn offset_preflight(&self, policy: &CurvePolicy) -> Classification<BezierOffsetPreflight2>;
fn fit_exact_line_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>>;
}
impl StagedBezierOffset for QuadraticBezier2 {
fn offset_preflight(&self, policy: &CurvePolicy) -> Classification<BezierOffsetPreflight2> {
QuadraticBezier2::offset_preflight(self, policy)
}
fn fit_exact_line_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
QuadraticBezier2::fit_exact_line_image(self, policy)
}
}
impl StagedBezierOffset for CubicBezier2 {
fn offset_preflight(&self, policy: &CurvePolicy) -> Classification<BezierOffsetPreflight2> {
CubicBezier2::offset_preflight(self, policy)
}
fn fit_exact_line_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
CubicBezier2::fit_exact_line_image(self, policy)
}
}
impl StagedBezierOffset for RationalQuadraticBezier2 {
fn offset_preflight(&self, policy: &CurvePolicy) -> Classification<BezierOffsetPreflight2> {
RationalQuadraticBezier2::offset_preflight(self, policy)
}
fn fit_exact_line_image(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
RationalQuadraticBezier2::fit_exact_line_image(self, policy)
}
}
fn staged_offset_left<C>(
curve: &C,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierOffsetCandidate2>>
where
C: StagedBezierOffset,
{
let preflight = match curve.offset_preflight(policy) {
Classification::Decided(preflight) => preflight,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let line_image_fit = match curve.fit_exact_line_image(policy) {
Ok(relation) => relation,
Err(CurveError::ZeroLengthLine)
if preflight.risks.contains(&BezierOffsetRisk::DegeneratePoint) =>
{
return Ok(Classification::Decided(
BezierOffsetCandidate2::Unresolved {
preflight,
distance,
},
));
}
Err(error) => return Err(error),
};
match line_image_fit {
Classification::Decided(BezierLineImageFitRelation::Fit(fit)) => Ok(
Classification::Decided(BezierOffsetCandidate2::ExactLineImage {
offset: fit.offset_left_exact(distance)?,
preflight,
}),
),
Classification::Decided(BezierLineImageFitRelation::NotLine) => Ok(
Classification::Decided(BezierOffsetCandidate2::Unresolved {
preflight,
distance,
}),
),
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
fn rational_endpoint_delta_status(first: &Point2, second: &Point2) -> ZeroStatus {
first.distance_squared(second).zero_status()
}
fn rational_collapsed_point_status(curve: &RationalQuadraticBezier2) -> ZeroStatus {
let start_control = curve
.start()
.distance_squared(curve.control())
.zero_status();
let control_end = curve.control().distance_squared(curve.end()).zero_status();
match (start_control, control_end) {
(ZeroStatus::Zero, ZeroStatus::Zero) => ZeroStatus::Zero,
(ZeroStatus::NonZero, _) | (_, ZeroStatus::NonZero) => ZeroStatus::NonZero,
_ => ZeroStatus::Unknown,
}
}
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 expected != sign {
return Some(false);
}
} else {
expected = Some(sign);
}
}
RealSign::Zero => return Some(false),
}
}
Some(expected.is_some())
}
fn build_preflight(
degree: BezierDegree,
cusp_classification: BezierCuspClassification,
inflection_classification: BezierInflectionClassification,
start_tangent_status: ZeroStatus,
end_tangent_status: ZeroStatus,
endpoint_coincidence: ZeroStatus,
policy: &CurvePolicy,
) -> BezierOffsetPreflight2 {
let mut risks = Vec::new();
match &cusp_classification {
BezierCuspClassification::DegeneratePoint => risks.push(BezierOffsetRisk::DegeneratePoint),
BezierCuspClassification::Cusps { .. } => risks.push(BezierOffsetRisk::Cusp),
BezierCuspClassification::Unresolved => risks.push(BezierOffsetRisk::Cusp),
BezierCuspClassification::None => {}
}
match &inflection_classification {
BezierInflectionClassification::Inflections { .. } => {
risks.push(BezierOffsetRisk::Inflection);
}
BezierInflectionClassification::AllCurvatureZero => {
risks.push(BezierOffsetRisk::AllCurvatureZero);
}
BezierInflectionClassification::Unresolved => risks.push(BezierOffsetRisk::Inflection),
BezierInflectionClassification::NotApplicable | BezierInflectionClassification::None => {}
}
push_endpoint_normal_risk(&mut risks, BezierEndpoint::Start, start_tangent_status);
push_endpoint_normal_risk(&mut risks, BezierEndpoint::End, end_tangent_status);
if endpoint_coincidence == ZeroStatus::Zero {
risks.push(BezierOffsetRisk::CoincidentEndpoints);
}
BezierOffsetPreflight2 {
degree,
cusp_classification,
inflection_classification,
start_tangent_status,
end_tangent_status,
endpoint_coincidence,
risks,
construction_policy: policy.clone(),
}
}
fn push_endpoint_normal_risk(
risks: &mut Vec<BezierOffsetRisk>,
endpoint: BezierEndpoint,
zero_status: ZeroStatus,
) {
match zero_status {
ZeroStatus::Zero => risks.push(BezierOffsetRisk::UndefinedEndpointNormal { endpoint }),
ZeroStatus::Unknown => risks.push(BezierOffsetRisk::UnresolvedEndpointNormal { endpoint }),
ZeroStatus::NonZero => {}
}
}