use std::cmp::Ordering;
use hyperreal::Real;
use crate::classify::{compare_reals, is_zero};
use crate::{
Aabb2, Axis2, BezierSubcurve2, Classification, CubicBezier2, CurveError, CurvePolicy,
CurveResult, Point2, QuadraticBezier2, RationalQuadraticBezier2, RetainedCurveCacheSummary2,
RetainedCurveFamily2, RetainedCurveIdentity2, RetainedCurvePeriodicity1, RetainedCurveProfile2,
RetainedEndpointEvidence2, RetainedParameterDomain1, RetainedTopologyStatus,
RetainedTrimInterval1, UncertaintyReason,
};
#[derive(Clone, Debug, PartialEq)]
pub struct PolynomialBSplineCurve2 {
degree: usize,
control_points: Vec<Point2>,
knots: Vec<Real>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PolynomialBSplineBezierExtraction2 {
degree: usize,
refined_control_points: Vec<Point2>,
refined_knots: Vec<Real>,
spans: Vec<BezierSubcurve2>,
inserted_knot_count: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RationalQuadraticBSplineCurve2 {
control_points: Vec<Point2>,
weights: Vec<Real>,
knots: Vec<Real>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RationalQuadraticBSplineBezierExtraction2 {
refined_control_points: Vec<Point2>,
refined_weights: Vec<Real>,
refined_knots: Vec<Real>,
spans: Vec<BezierSubcurve2>,
inserted_knot_count: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RationalBSplineCurve2 {
degree: usize,
control_points: Vec<Point2>,
weights: Vec<Real>,
knots: Vec<Real>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RationalBSplineBezierExtraction2 {
degree: usize,
refined_control_points: Vec<Point2>,
refined_weights: Vec<Real>,
refined_knots: Vec<Real>,
spans: Vec<RationalBezierSpan2>,
inserted_knot_count: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RationalBSplineNativeTopologyReport2 {
span_reports: Vec<RationalBezierSpanTopologyReport2>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RationalBezierSpanTopologyReport2 {
span_index: usize,
degree: usize,
knot_start: Real,
knot_end: Real,
status: RetainedTopologyStatus,
native_subcurve: Option<BezierSubcurve2>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetainedSpanAxisMonotonicity {
CertifiedMonotone,
HasInteriorExtrema,
Unsupported,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RetainedSpanWeightDomainReport2 {
weight_count: usize,
certified_nonzero_count: usize,
all_weights_certified_nonzero: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedBSplineSpanFacts2 {
span_index: usize,
knot_start: Real,
knot_end: Real,
bounds: Aabb2,
x_monotonicity: RetainedSpanAxisMonotonicity,
y_monotonicity: RetainedSpanAxisMonotonicity,
topology_status: RetainedTopologyStatus,
weight_domain: Option<RetainedSpanWeightDomainReport2>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedBSplineSpanFactReport2 {
span_facts: Vec<RetainedBSplineSpanFacts2>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RationalBezierSpan2 {
degree: usize,
control_points: Vec<Point2>,
weights: Vec<Real>,
knot_start: Real,
knot_end: Real,
}
impl PolynomialBSplineCurve2 {
pub fn try_new(
degree: usize,
control_points: Vec<Point2>,
knots: Vec<Real>,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
if !(2..=3).contains(°ree)
|| control_points.len() < degree + 1
|| knots.len() != control_points.len() + degree + 1
{
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
match validate_nondecreasing_knots(&knots, policy) {
Classification::Decided(()) => {}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
if !endpoint_multiplicity_is_clamped(&knots, degree, policy)? {
return Err(CurveError::InvalidBSpline);
}
if !has_positive_span(&knots, degree, control_points.len(), policy)? {
return Err(CurveError::InvalidBSpline);
}
Ok(Classification::Decided(Self {
degree,
control_points,
knots,
}))
}
pub const fn degree(&self) -> usize {
self.degree
}
pub fn control_points(&self) -> &[Point2] {
&self.control_points
}
pub fn knots(&self) -> &[Real] {
&self.knots
}
pub fn extract_bezier_spans(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<PolynomialBSplineBezierExtraction2>> {
let mut refined = BSplineWorkingCurve {
degree: self.degree,
control_points: self.control_points.clone(),
knots: self.knots.clone(),
inserted_knot_count: 0,
};
let interior_knots = match distinct_interior_knots(&refined.knots, self.degree, policy) {
Classification::Decided(knots) => knots,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
for knot in interior_knots {
loop {
let multiplicity = knot_multiplicity(&refined.knots, &knot, policy)?;
if multiplicity >= self.degree {
break;
}
match refined.insert_knot(knot.clone(), policy)? {
Classification::Decided(()) => {}
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
}
}
}
let spans = match extract_refined_bezier_spans(&refined, policy)? {
Classification::Decided(spans) => spans,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(
PolynomialBSplineBezierExtraction2 {
degree: self.degree,
refined_control_points: refined.control_points,
refined_knots: refined.knots,
spans,
inserted_knot_count: refined.inserted_knot_count,
},
))
}
pub fn retained_curve_profile(
&self,
source_index: u64,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedCurveProfile2>> {
let domain = match bspline_parameter_domain(
&self.knots,
self.degree,
self.control_points.len(),
policy,
)? {
Classification::Decided(domain) => domain,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let trim = match default_trim(&domain, policy)? {
Classification::Decided(trim) => trim,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let extraction = match self.extract_bezier_spans(policy)? {
Classification::Decided(extraction) => extraction,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let cache_summary = RetainedCurveCacheSummary2::new(
self.control_points.len(),
self.knots.len(),
extraction.spans().len(),
extraction.spans().len(),
0,
)?;
Ok(Classification::Decided(RetainedCurveProfile2::new(
RetainedCurveIdentity2::new(RetainedCurveFamily2::PolynomialBSpline, source_index),
domain.clone(),
trim,
RetainedCurvePeriodicity1::NonPeriodic,
RetainedTopologyStatus::NativeExact,
endpoint_evidence(&self.control_points, &domain)?,
cache_summary,
)?))
}
}
impl PolynomialBSplineBezierExtraction2 {
pub const fn degree(&self) -> usize {
self.degree
}
pub fn refined_control_points(&self) -> &[Point2] {
&self.refined_control_points
}
pub fn refined_knots(&self) -> &[Real] {
&self.refined_knots
}
pub fn spans(&self) -> &[BezierSubcurve2] {
&self.spans
}
pub const fn inserted_knot_count(&self) -> usize {
self.inserted_knot_count
}
pub fn span_fact_report(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedBSplineSpanFactReport2>> {
native_span_fact_report(&self.spans, &self.refined_knots, self.degree, policy)
}
}
impl RationalQuadraticBSplineCurve2 {
pub fn try_new(
control_points: Vec<Point2>,
weights: Vec<Real>,
knots: Vec<Real>,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
let degree = 2;
if control_points.len() != weights.len()
|| control_points.len() < degree + 1
|| knots.len() != control_points.len() + degree + 1
{
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
for weight in &weights {
match is_zero(weight, policy) {
Some(false) => {}
Some(true) => return Err(CurveError::ZeroRationalBezierWeight),
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
match validate_nondecreasing_knots(&knots, policy) {
Classification::Decided(()) => {}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
if !endpoint_multiplicity_is_clamped(&knots, degree, policy)? {
return Err(CurveError::InvalidBSpline);
}
if !has_positive_span(&knots, degree, control_points.len(), policy)? {
return Err(CurveError::InvalidBSpline);
}
Ok(Classification::Decided(Self {
control_points,
weights,
knots,
}))
}
pub fn control_points(&self) -> &[Point2] {
&self.control_points
}
pub fn weights(&self) -> &[Real] {
&self.weights
}
pub fn knots(&self) -> &[Real] {
&self.knots
}
pub fn extract_bezier_spans(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<RationalQuadraticBSplineBezierExtraction2>> {
let mut refined = HomogeneousBSplineWorkingCurve {
degree: 2,
controls: self
.control_points
.iter()
.zip(&self.weights)
.map(|(point, weight)| HomogeneousControl2::from_affine(point, weight))
.collect(),
knots: self.knots.clone(),
inserted_knot_count: 0,
};
let interior_knots = match distinct_interior_knots(&refined.knots, 2, policy) {
Classification::Decided(knots) => knots,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
for knot in interior_knots {
loop {
let multiplicity = knot_multiplicity(&refined.knots, &knot, policy)?;
if multiplicity >= 2 {
break;
}
match refined.insert_knot(knot.clone(), policy)? {
Classification::Decided(()) => {}
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
}
}
}
let extraction = match extract_refined_rational_quadratic_spans(&refined, policy)? {
Classification::Decided(extraction) => extraction,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(extraction))
}
pub fn retained_curve_profile(
&self,
source_index: u64,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedCurveProfile2>> {
let domain =
match bspline_parameter_domain(&self.knots, 2, self.control_points.len(), policy)? {
Classification::Decided(domain) => domain,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let trim = match default_trim(&domain, policy)? {
Classification::Decided(trim) => trim,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let extraction = match self.extract_bezier_spans(policy)? {
Classification::Decided(extraction) => extraction,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let cache_summary = RetainedCurveCacheSummary2::new(
self.control_points.len(),
self.knots.len(),
extraction.spans().len(),
extraction.spans().len(),
0,
)?;
Ok(Classification::Decided(RetainedCurveProfile2::new(
RetainedCurveIdentity2::new(
RetainedCurveFamily2::RationalQuadraticBSpline,
source_index,
),
domain.clone(),
trim,
RetainedCurvePeriodicity1::NonPeriodic,
RetainedTopologyStatus::NativeExact,
endpoint_evidence(&self.control_points, &domain)?,
cache_summary,
)?))
}
}
impl RationalQuadraticBSplineBezierExtraction2 {
pub fn refined_control_points(&self) -> &[Point2] {
&self.refined_control_points
}
pub fn refined_weights(&self) -> &[Real] {
&self.refined_weights
}
pub fn refined_knots(&self) -> &[Real] {
&self.refined_knots
}
pub fn spans(&self) -> &[BezierSubcurve2] {
&self.spans
}
pub const fn inserted_knot_count(&self) -> usize {
self.inserted_knot_count
}
pub fn span_fact_report(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedBSplineSpanFactReport2>> {
let mut report = match native_span_fact_report(&self.spans, &self.refined_knots, 2, policy)?
{
Classification::Decided(report) => report,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let mut fact_index = 0_usize;
for knot_index in 2..self.refined_knots.len().saturating_sub(1) {
if compare_reals(
&self.refined_knots[knot_index],
&self.refined_knots[knot_index + 1],
policy,
) != Some(Ordering::Less)
{
continue;
}
let Some(fact) = report.span_facts.get_mut(fact_index) else {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
};
let start = knot_index - 2;
fact.weight_domain = Some(weight_domain_report(
&self.refined_weights[start..=knot_index],
policy,
)?);
fact_index += 1;
}
if fact_index != report.span_facts.len() {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
Ok(Classification::Decided(report))
}
}
impl RationalBSplineCurve2 {
pub fn try_new(
degree: usize,
control_points: Vec<Point2>,
weights: Vec<Real>,
knots: Vec<Real>,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
let Some(order) = degree.checked_add(1) else {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
};
let Some(expected_knot_count) = control_points.len().checked_add(order) else {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
};
if degree < 2
|| control_points.len() != weights.len()
|| control_points.len() < order
|| knots.len() != expected_knot_count
{
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
for weight in &weights {
match is_zero(weight, policy) {
Some(false) => {}
Some(true) => return Err(CurveError::ZeroRationalBezierWeight),
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
match validate_nondecreasing_knots(&knots, policy) {
Classification::Decided(()) => {}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
if !endpoint_multiplicity_is_clamped(&knots, degree, policy)? {
return Err(CurveError::InvalidBSpline);
}
if !has_positive_span(&knots, degree, control_points.len(), policy)? {
return Err(CurveError::InvalidBSpline);
}
Ok(Classification::Decided(Self {
degree,
control_points,
weights,
knots,
}))
}
pub const fn degree(&self) -> usize {
self.degree
}
pub fn control_points(&self) -> &[Point2] {
&self.control_points
}
pub fn weights(&self) -> &[Real] {
&self.weights
}
pub fn knots(&self) -> &[Real] {
&self.knots
}
pub fn extract_bezier_spans(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<RationalBSplineBezierExtraction2>> {
let mut refined = HomogeneousBSplineWorkingCurve {
degree: self.degree,
controls: self
.control_points
.iter()
.zip(&self.weights)
.map(|(point, weight)| HomogeneousControl2::from_affine(point, weight))
.collect(),
knots: self.knots.clone(),
inserted_knot_count: 0,
};
let interior_knots = match distinct_interior_knots(&refined.knots, self.degree, policy) {
Classification::Decided(knots) => knots,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
for knot in interior_knots {
loop {
let multiplicity = knot_multiplicity(&refined.knots, &knot, policy)?;
if multiplicity >= self.degree {
break;
}
match refined.insert_knot(knot.clone(), policy)? {
Classification::Decided(()) => {}
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
}
}
}
extract_refined_rational_spans(&refined, policy)
}
pub fn retained_curve_profile(
&self,
source_index: u64,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedCurveProfile2>> {
let domain = match bspline_parameter_domain(
&self.knots,
self.degree,
self.control_points.len(),
policy,
)? {
Classification::Decided(domain) => domain,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let trim = match default_trim(&domain, policy)? {
Classification::Decided(trim) => trim,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let extraction = match self.extract_bezier_spans(policy)? {
Classification::Decided(extraction) => extraction,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let report = match extraction.native_topology_report(policy)? {
Classification::Decided(report) => report,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let native_span_count = report
.span_reports()
.iter()
.filter(|span| span.status().is_native_exact())
.count();
let retained_span_count = report.span_reports().len() - native_span_count;
let topology_status = if report.is_fully_native_exact() {
RetainedTopologyStatus::NativeExact
} else {
RetainedTopologyStatus::Unsupported
};
let cache_summary = RetainedCurveCacheSummary2::new(
self.control_points.len(),
self.knots.len(),
report.span_reports().len(),
native_span_count,
retained_span_count,
)?;
Ok(Classification::Decided(RetainedCurveProfile2::new(
RetainedCurveIdentity2::new(RetainedCurveFamily2::RationalBSpline, source_index),
domain.clone(),
trim,
RetainedCurvePeriodicity1::NonPeriodic,
topology_status,
endpoint_evidence(&self.control_points, &domain)?,
cache_summary,
)?))
}
}
impl RationalBSplineBezierExtraction2 {
pub const fn degree(&self) -> usize {
self.degree
}
pub fn refined_control_points(&self) -> &[Point2] {
&self.refined_control_points
}
pub fn refined_weights(&self) -> &[Real] {
&self.refined_weights
}
pub fn refined_knots(&self) -> &[Real] {
&self.refined_knots
}
pub fn spans(&self) -> &[RationalBezierSpan2] {
&self.spans
}
pub fn native_subcurves(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<BezierSubcurve2>>> {
let report = match self.native_topology_report(policy)? {
Classification::Decided(report) => report,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
if !report.is_fully_native_exact() {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
Ok(Classification::Decided(report.into_native_subcurves()))
}
pub fn native_topology_report(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<RationalBSplineNativeTopologyReport2>> {
let mut span_reports = Vec::with_capacity(self.spans.len());
for (span_index, span) in self.spans.iter().enumerate() {
match span.native_topology_report(span_index, policy)? {
Classification::Decided(report) => span_reports.push(report),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
Ok(Classification::Decided(
RationalBSplineNativeTopologyReport2::new(span_reports)?,
))
}
pub const fn inserted_knot_count(&self) -> usize {
self.inserted_knot_count
}
pub fn span_fact_report(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedBSplineSpanFactReport2>> {
let topology = match self.native_topology_report(policy)? {
Classification::Decided(report) => report,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let mut facts = Vec::with_capacity(self.spans.len());
for (span_index, span) in self.spans.iter().enumerate() {
let topology_report = &topology.span_reports()[span_index];
let (bounds, x_monotonicity, y_monotonicity) =
if let Some(native) = topology_report.native_subcurve() {
let bounds = match subcurve_certified_bounds(native, policy) {
Classification::Decided(bounds) => bounds,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
(
bounds,
match subcurve_axis_monotonicity(native, Axis2::X, policy) {
Classification::Decided(monotonicity) => monotonicity,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
},
match subcurve_axis_monotonicity(native, Axis2::Y, policy) {
Classification::Decided(monotonicity) => monotonicity,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
},
)
} else {
let bounds = match Aabb2::from_points(span.control_points(), policy) {
Classification::Decided(bounds) => bounds,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
(
bounds,
RetainedSpanAxisMonotonicity::Unsupported,
RetainedSpanAxisMonotonicity::Unsupported,
)
};
facts.push(RetainedBSplineSpanFacts2::new(
span_index,
span.knot_start.clone(),
span.knot_end.clone(),
bounds,
x_monotonicity,
y_monotonicity,
topology_report.status(),
Some(weight_domain_report(span.weights(), policy)?),
)?);
}
Ok(Classification::Decided(
RetainedBSplineSpanFactReport2::new(facts)?,
))
}
}
impl RetainedSpanWeightDomainReport2 {
pub fn new(
weight_count: usize,
certified_nonzero_count: usize,
all_weights_certified_nonzero: bool,
) -> CurveResult<Self> {
validate_weight_domain_report(
weight_count,
certified_nonzero_count,
all_weights_certified_nonzero,
)?;
Ok(Self {
weight_count,
certified_nonzero_count,
all_weights_certified_nonzero,
})
}
pub const fn weight_count(&self) -> usize {
self.weight_count
}
pub const fn certified_nonzero_count(&self) -> usize {
self.certified_nonzero_count
}
pub const fn all_weights_certified_nonzero(&self) -> bool {
self.all_weights_certified_nonzero
}
}
impl RetainedBSplineSpanFacts2 {
#[allow(clippy::too_many_arguments)]
pub fn new(
span_index: usize,
knot_start: Real,
knot_end: Real,
bounds: Aabb2,
x_monotonicity: RetainedSpanAxisMonotonicity,
y_monotonicity: RetainedSpanAxisMonotonicity,
topology_status: RetainedTopologyStatus,
weight_domain: Option<RetainedSpanWeightDomainReport2>,
) -> CurveResult<Self> {
validate_span_fact_evidence(
&knot_start,
&knot_end,
&bounds,
topology_status,
x_monotonicity,
y_monotonicity,
weight_domain.as_ref(),
)?;
Ok(Self {
span_index,
knot_start,
knot_end,
bounds,
x_monotonicity,
y_monotonicity,
topology_status,
weight_domain,
})
}
pub const fn span_index(&self) -> usize {
self.span_index
}
pub fn knot_interval(&self) -> (&Real, &Real) {
(&self.knot_start, &self.knot_end)
}
pub const fn bounds(&self) -> &Aabb2 {
&self.bounds
}
pub const fn x_monotonicity(&self) -> RetainedSpanAxisMonotonicity {
self.x_monotonicity
}
pub const fn y_monotonicity(&self) -> RetainedSpanAxisMonotonicity {
self.y_monotonicity
}
pub const fn topology_status(&self) -> RetainedTopologyStatus {
self.topology_status
}
pub const fn weight_domain(&self) -> Option<&RetainedSpanWeightDomainReport2> {
self.weight_domain.as_ref()
}
}
impl RetainedBSplineSpanFactReport2 {
pub fn new(span_facts: Vec<RetainedBSplineSpanFacts2>) -> CurveResult<Self> {
validate_span_fact_report_evidence(&span_facts)?;
Ok(Self { span_facts })
}
pub fn span_facts(&self) -> &[RetainedBSplineSpanFacts2] {
&self.span_facts
}
}
impl RationalBSplineNativeTopologyReport2 {
pub fn new(span_reports: Vec<RationalBezierSpanTopologyReport2>) -> CurveResult<Self> {
validate_span_topology_report_evidence(&span_reports)?;
Ok(Self { span_reports })
}
pub fn span_reports(&self) -> &[RationalBezierSpanTopologyReport2] {
&self.span_reports
}
pub fn is_fully_native_exact(&self) -> bool {
self.span_reports
.iter()
.all(|report| report.status().is_native_exact())
}
pub fn into_native_subcurves(self) -> Vec<BezierSubcurve2> {
self.span_reports
.into_iter()
.filter_map(|report| report.native_subcurve)
.collect()
}
}
impl RationalBezierSpanTopologyReport2 {
pub fn new(
span_index: usize,
degree: usize,
knot_start: Real,
knot_end: Real,
status: RetainedTopologyStatus,
native_subcurve: Option<BezierSubcurve2>,
) -> CurveResult<Self> {
validate_rational_span_topology_evidence(
degree,
&knot_start,
&knot_end,
status,
native_subcurve.as_ref(),
)?;
Ok(Self {
span_index,
degree,
knot_start,
knot_end,
status,
native_subcurve,
})
}
pub const fn span_index(&self) -> usize {
self.span_index
}
pub const fn degree(&self) -> usize {
self.degree
}
pub fn knot_interval(&self) -> (&Real, &Real) {
(&self.knot_start, &self.knot_end)
}
pub const fn status(&self) -> RetainedTopologyStatus {
self.status
}
pub const fn native_subcurve(&self) -> Option<&BezierSubcurve2> {
self.native_subcurve.as_ref()
}
}
fn validate_weight_domain_report(
weight_count: usize,
certified_nonzero_count: usize,
all_weights_certified_nonzero: bool,
) -> CurveResult<()> {
if weight_count == 0 || certified_nonzero_count > weight_count {
return Err(CurveError::Topology(
"retained span weight report count evidence is inconsistent".into(),
));
}
if all_weights_certified_nonzero != (certified_nonzero_count == weight_count) {
return Err(CurveError::Topology(
"retained span weight report all-nonzero flag does not match certified count".into(),
));
}
Ok(())
}
fn validate_span_fact_evidence(
knot_start: &Real,
knot_end: &Real,
bounds: &Aabb2,
topology_status: RetainedTopologyStatus,
x_monotonicity: RetainedSpanAxisMonotonicity,
y_monotonicity: RetainedSpanAxisMonotonicity,
weight_domain: Option<&RetainedSpanWeightDomainReport2>,
) -> CurveResult<()> {
validate_positive_knot_interval(knot_start, knot_end)?;
match bounds.has_valid_ordering(&CurvePolicy::certified()) {
Classification::Decided(true) => {}
Classification::Decided(false) => {
return Err(CurveError::Topology(
"retained span facts must carry a well-ordered bounding box".into(),
));
}
Classification::Uncertain(reason) => {
return Err(CurveError::Topology(format!(
"retained span fact bounds ordering is uncertified: {reason:?}"
)));
}
}
if !topology_status.is_native_exact()
&& (x_monotonicity != RetainedSpanAxisMonotonicity::Unsupported
|| y_monotonicity != RetainedSpanAxisMonotonicity::Unsupported)
{
return Err(CurveError::Topology(
"non-native retained span facts must not claim certified monotonicity".into(),
));
}
if !topology_status.is_native_exact() && !topology_status.is_retained_evidence() {
return Err(CurveError::Topology(
"retained B-spline span facts must carry exact native or retained evidence status"
.into(),
));
}
if topology_status.is_retained_evidence() && weight_domain.is_none() {
return Err(CurveError::Topology(
"retained non-native B-spline span facts must carry rational weight-domain evidence"
.into(),
));
}
if topology_status.is_native_exact()
&& (x_monotonicity == RetainedSpanAxisMonotonicity::Unsupported
|| y_monotonicity == RetainedSpanAxisMonotonicity::Unsupported)
{
return Err(CurveError::Topology(
"native retained span facts must carry exact monotonicity evidence".into(),
));
}
if topology_status.is_native_exact()
&& weight_domain.is_some_and(|domain| !domain.all_weights_certified_nonzero())
{
return Err(CurveError::Topology(
"native retained rational span facts must carry all-nonzero weight evidence".into(),
));
}
Ok(())
}
fn validate_span_fact_report_evidence(span_facts: &[RetainedBSplineSpanFacts2]) -> CurveResult<()> {
if span_facts.is_empty() {
return Err(CurveError::Topology(
"retained span fact report must carry at least one span".into(),
));
}
let policy = CurvePolicy::certified();
for (expected_index, fact) in span_facts.iter().enumerate() {
if fact.span_index() != expected_index {
return Err(CurveError::Topology(
"retained span fact report indices must be contiguous".into(),
));
}
if let Some(previous) = expected_index
.checked_sub(1)
.and_then(|index| span_facts.get(index))
{
validate_adjacent_knot_windows(
previous.knot_interval().1,
fact.knot_interval().0,
&policy,
"retained span fact report knot intervals must be contiguous",
)?;
}
}
Ok(())
}
fn validate_span_topology_report_evidence(
span_reports: &[RationalBezierSpanTopologyReport2],
) -> CurveResult<()> {
if span_reports.is_empty() {
return Err(CurveError::Topology(
"retained span topology report must carry at least one span".into(),
));
}
let degree = span_reports[0].degree();
let policy = CurvePolicy::certified();
for (expected_index, report) in span_reports.iter().enumerate() {
if report.span_index() != expected_index {
return Err(CurveError::Topology(
"retained span topology report indices must be contiguous".into(),
));
}
if report.degree() != degree {
return Err(CurveError::Topology(
"retained span topology report degrees must match".into(),
));
}
if let Some(previous) = expected_index
.checked_sub(1)
.and_then(|index| span_reports.get(index))
{
validate_adjacent_knot_windows(
previous.knot_interval().1,
report.knot_interval().0,
&policy,
"retained span topology report knot intervals must be contiguous",
)?;
}
}
Ok(())
}
fn validate_rational_span_topology_evidence(
degree: usize,
knot_start: &Real,
knot_end: &Real,
status: RetainedTopologyStatus,
native_subcurve: Option<&BezierSubcurve2>,
) -> CurveResult<()> {
validate_positive_knot_interval(knot_start, knot_end)?;
if degree < 2 {
return Err(CurveError::Topology(
"retained rational span topology report degree must be at least two".into(),
));
}
match (status.is_native_exact(), native_subcurve) {
(true, Some(BezierSubcurve2::RationalQuadratic(_))) if degree == 2 => Ok(()),
(true, Some(BezierSubcurve2::Cubic(_))) if degree == 3 => Ok(()),
(true, Some(_)) => Err(CurveError::Topology(
"native rational span topology report subcurve does not match retained degree".into(),
)),
(true, None) => Err(CurveError::Topology(
"native rational span topology report must carry a native subcurve".into(),
)),
(false, Some(_)) => Err(CurveError::Topology(
"non-native rational span topology report must not carry a native subcurve".into(),
)),
(false, None) => Ok(()),
}
}
fn validate_positive_knot_interval(knot_start: &Real, knot_end: &Real) -> CurveResult<()> {
let policy = CurvePolicy::certified();
if compare_reals(knot_start, knot_end, &policy) != Some(Ordering::Less) {
return Err(CurveError::Topology(
"retained B-spline span report must carry certified positive knot interval".into(),
));
}
Ok(())
}
fn validate_adjacent_knot_windows(
previous_end: &Real,
next_start: &Real,
policy: &CurvePolicy,
message: &str,
) -> CurveResult<()> {
if compare_reals(previous_end, next_start, policy) != Some(Ordering::Equal) {
return Err(CurveError::Topology(message.into()));
}
Ok(())
}
impl RationalBezierSpan2 {
pub const fn degree(&self) -> usize {
self.degree
}
pub fn control_points(&self) -> &[Point2] {
&self.control_points
}
pub fn weights(&self) -> &[Real] {
&self.weights
}
pub fn knot_interval(&self) -> (&Real, &Real) {
(&self.knot_start, &self.knot_end)
}
pub fn native_subcurve(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierSubcurve2>> {
match self.native_topology_report(0, policy)? {
Classification::Decided(report) => match report.native_subcurve {
Some(subcurve) => Ok(Classification::Decided(subcurve)),
None => Ok(Classification::Uncertain(UncertaintyReason::Unsupported)),
},
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
pub fn native_topology_report(
&self,
span_index: usize,
policy: &CurvePolicy,
) -> CurveResult<Classification<RationalBezierSpanTopologyReport2>> {
if self.control_points.len() != self.degree + 1 || self.weights.len() != self.degree + 1 {
return Ok(Classification::Decided(
RationalBezierSpanTopologyReport2::new(
span_index,
self.degree,
self.knot_start.clone(),
self.knot_end.clone(),
RetainedTopologyStatus::Unsupported,
None,
)?,
));
}
match self.degree {
2 => {
let curve = RationalQuadraticBezier2::try_new(
self.control_points[0].clone(),
self.control_points[1].clone(),
self.control_points[2].clone(),
self.weights[0].clone(),
self.weights[1].clone(),
self.weights[2].clone(),
)?;
Ok(Classification::Decided(
RationalBezierSpanTopologyReport2::new(
span_index,
self.degree,
self.knot_start.clone(),
self.knot_end.clone(),
RetainedTopologyStatus::NativeExact,
Some(BezierSubcurve2::RationalQuadratic(curve)),
)?,
))
}
3 => match weights_are_all_equal(&self.weights, policy) {
Classification::Decided(true) => Ok(Classification::Decided(
RationalBezierSpanTopologyReport2::new(
span_index,
self.degree,
self.knot_start.clone(),
self.knot_end.clone(),
RetainedTopologyStatus::NativeExact,
Some(BezierSubcurve2::Cubic(CubicBezier2::new(
self.control_points[0].clone(),
self.control_points[1].clone(),
self.control_points[2].clone(),
self.control_points[3].clone(),
))),
)?,
)),
Classification::Decided(false) => Ok(Classification::Decided(
RationalBezierSpanTopologyReport2::new(
span_index,
self.degree,
self.knot_start.clone(),
self.knot_end.clone(),
RetainedTopologyStatus::Unsupported,
None,
)?,
)),
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
},
_ => Ok(Classification::Decided(
RationalBezierSpanTopologyReport2::new(
span_index,
self.degree,
self.knot_start.clone(),
self.knot_end.clone(),
RetainedTopologyStatus::Unsupported,
None,
)?,
)),
}
}
}
#[derive(Clone, Debug)]
struct BSplineWorkingCurve {
degree: usize,
control_points: Vec<Point2>,
knots: Vec<Real>,
inserted_knot_count: usize,
}
#[derive(Clone, Debug)]
struct HomogeneousControl2 {
x: Real,
y: Real,
weight: Real,
}
#[derive(Clone, Debug)]
struct HomogeneousBSplineWorkingCurve {
degree: usize,
controls: Vec<HomogeneousControl2>,
knots: Vec<Real>,
inserted_knot_count: usize,
}
impl HomogeneousControl2 {
fn from_affine(point: &Point2, weight: &Real) -> Self {
Self {
x: point.x() * weight,
y: point.y() * weight,
weight: weight.clone(),
}
}
fn lerp(&self, other: &Self, t: Real) -> Self {
let one_minus_t = Real::one() - &t;
Self {
x: (&self.x * &one_minus_t) + (&other.x * &t),
y: (&self.y * &one_minus_t) + (&other.y * &t),
weight: (&self.weight * &one_minus_t) + (&other.weight * &t),
}
}
fn to_affine(&self, policy: &CurvePolicy) -> CurveResult<Classification<(Point2, Real)>> {
match is_zero(&self.weight, policy) {
Some(false) => {}
Some(true) => return Err(CurveError::ZeroRationalBezierWeight),
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
let x = (&self.x / &self.weight)?;
let y = (&self.y / &self.weight)?;
Ok(Classification::Decided((
Point2::new(x, y),
self.weight.clone(),
)))
}
}
impl BSplineWorkingCurve {
fn insert_knot(&mut self, knot: Real, policy: &CurvePolicy) -> CurveResult<Classification<()>> {
let Some(span) = find_insertion_span(
&self.knots,
self.degree,
self.control_points.len(),
&knot,
policy,
)?
else {
return Ok(Classification::Uncertain(UncertaintyReason::Ordering));
};
let multiplicity = knot_multiplicity(&self.knots, &knot, policy)?;
if multiplicity >= self.degree {
return Ok(Classification::Decided(()));
}
let n = self.control_points.len() - 1;
let p = self.degree;
let mut new_points = vec![self.control_points[0].clone(); self.control_points.len() + 1];
for (i, point) in new_points
.iter_mut()
.enumerate()
.take(span.saturating_sub(p) + 1)
{
*point = self.control_points[i].clone();
}
let right_start = span - multiplicity + 1;
new_points[right_start..=n + 1].clone_from_slice(&self.control_points[right_start - 1..=n]);
for (i, point) in new_points
.iter_mut()
.enumerate()
.take(span - multiplicity + 1)
.skip(span - p + 1)
{
let denominator = &self.knots[i + p] - &self.knots[i];
let alpha = match (knot.clone() - &self.knots[i]) / denominator {
Ok(alpha) => alpha,
Err(_) => return Ok(Classification::Uncertain(UncertaintyReason::Boundary)),
};
*point = self.control_points[i - 1].lerp(&self.control_points[i], alpha);
}
self.knots.insert(span + 1, knot);
self.control_points = new_points;
self.inserted_knot_count += 1;
Ok(Classification::Decided(()))
}
}
impl HomogeneousBSplineWorkingCurve {
fn insert_knot(&mut self, knot: Real, policy: &CurvePolicy) -> CurveResult<Classification<()>> {
let Some(span) =
find_insertion_span(&self.knots, self.degree, self.controls.len(), &knot, policy)?
else {
return Ok(Classification::Uncertain(UncertaintyReason::Ordering));
};
let multiplicity = knot_multiplicity(&self.knots, &knot, policy)?;
if multiplicity >= self.degree {
return Ok(Classification::Decided(()));
}
let n = self.controls.len() - 1;
let p = self.degree;
let mut new_controls = vec![self.controls[0].clone(); self.controls.len() + 1];
for (i, control) in new_controls
.iter_mut()
.enumerate()
.take(span.saturating_sub(p) + 1)
{
*control = self.controls[i].clone();
}
let right_start = span - multiplicity + 1;
new_controls[right_start..=n + 1].clone_from_slice(&self.controls[right_start - 1..=n]);
for (i, control) in new_controls
.iter_mut()
.enumerate()
.take(span - multiplicity + 1)
.skip(span - p + 1)
{
let denominator = &self.knots[i + p] - &self.knots[i];
let alpha = match (knot.clone() - &self.knots[i]) / denominator {
Ok(alpha) => alpha,
Err(_) => return Ok(Classification::Uncertain(UncertaintyReason::Boundary)),
};
*control = self.controls[i - 1].lerp(&self.controls[i], alpha);
}
self.knots.insert(span + 1, knot);
self.controls = new_controls;
self.inserted_knot_count += 1;
Ok(Classification::Decided(()))
}
}
fn validate_nondecreasing_knots(knots: &[Real], policy: &CurvePolicy) -> Classification<()> {
for pair in knots.windows(2) {
match compare_reals(&pair[0], &pair[1], policy) {
Some(Ordering::Less | Ordering::Equal) => {}
Some(Ordering::Greater) => {
return Classification::Uncertain(UncertaintyReason::Ordering);
}
None => return Classification::Uncertain(UncertaintyReason::Ordering),
}
}
Classification::Decided(())
}
fn endpoint_multiplicity_is_clamped(
knots: &[Real],
degree: usize,
policy: &CurvePolicy,
) -> CurveResult<bool> {
let first = knots.first().ok_or(CurveError::InvalidBSpline)?;
let last = knots.last().ok_or(CurveError::InvalidBSpline)?;
Ok(knot_multiplicity(knots, first, policy)? == degree + 1
&& knot_multiplicity(knots, last, policy)? == degree + 1)
}
fn has_positive_span(
knots: &[Real],
degree: usize,
control_count: usize,
policy: &CurvePolicy,
) -> CurveResult<bool> {
for i in degree..control_count {
if compare_reals(&knots[i], &knots[i + 1], policy) == Some(Ordering::Less) {
return Ok(true);
}
}
Ok(false)
}
fn native_span_fact_report(
spans: &[BezierSubcurve2],
refined_knots: &[Real],
degree: usize,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedBSplineSpanFactReport2>> {
let mut facts = Vec::with_capacity(spans.len());
let mut span_index = 0_usize;
for knot_index in degree..refined_knots.len().saturating_sub(1) {
if compare_reals(
&refined_knots[knot_index],
&refined_knots[knot_index + 1],
policy,
) != Some(Ordering::Less)
{
continue;
}
let Some(span) = spans.get(span_index) else {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
};
let bounds = match subcurve_certified_bounds(span, policy) {
Classification::Decided(bounds) => bounds,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
facts.push(RetainedBSplineSpanFacts2::new(
span_index,
refined_knots[knot_index].clone(),
refined_knots[knot_index + 1].clone(),
bounds,
match subcurve_axis_monotonicity(span, Axis2::X, policy) {
Classification::Decided(monotonicity) => monotonicity,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
},
match subcurve_axis_monotonicity(span, Axis2::Y, policy) {
Classification::Decided(monotonicity) => monotonicity,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
},
RetainedTopologyStatus::NativeExact,
None,
)?);
span_index += 1;
}
if span_index != spans.len() {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
Ok(Classification::Decided(
RetainedBSplineSpanFactReport2::new(facts)?,
))
}
fn subcurve_certified_bounds(
curve: &BezierSubcurve2,
policy: &CurvePolicy,
) -> Classification<Aabb2> {
match curve {
BezierSubcurve2::Quadratic(curve) => curve.certified_bounds(policy),
BezierSubcurve2::Cubic(curve) => curve.certified_bounds(policy),
BezierSubcurve2::RationalQuadratic(curve) => curve.certified_bounds(policy),
}
}
fn subcurve_axis_monotonicity(
curve: &BezierSubcurve2,
axis: Axis2,
policy: &CurvePolicy,
) -> Classification<RetainedSpanAxisMonotonicity> {
let roots = match curve {
BezierSubcurve2::Quadratic(curve) => curve.axis_monotone_parameters(axis, policy),
BezierSubcurve2::Cubic(curve) => curve.axis_monotone_parameters(axis, policy),
BezierSubcurve2::RationalQuadratic(curve) => curve.axis_monotone_parameters(axis, policy),
};
match roots {
Classification::Decided(roots) if roots.is_empty() => {
Classification::Decided(RetainedSpanAxisMonotonicity::CertifiedMonotone)
}
Classification::Decided(_) => {
Classification::Decided(RetainedSpanAxisMonotonicity::HasInteriorExtrema)
}
Classification::Uncertain(reason) => Classification::Uncertain(reason),
}
}
fn weight_domain_report(
weights: &[Real],
policy: &CurvePolicy,
) -> CurveResult<RetainedSpanWeightDomainReport2> {
let mut certified_nonzero_count = 0_usize;
for weight in weights {
match is_zero(weight, policy) {
Some(false) => certified_nonzero_count += 1,
Some(true) => return Err(CurveError::ZeroRationalBezierWeight),
None => {}
}
}
RetainedSpanWeightDomainReport2::new(
weights.len(),
certified_nonzero_count,
certified_nonzero_count == weights.len(),
)
}
fn bspline_parameter_domain(
knots: &[Real],
degree: usize,
control_count: usize,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedParameterDomain1>> {
let Some(start) = knots.get(degree) else {
return Err(CurveError::InvalidBSpline);
};
let Some(end) = knots.get(control_count) else {
return Err(CurveError::InvalidBSpline);
};
RetainedParameterDomain1::try_new(start.clone(), end.clone(), policy)
}
fn default_trim(
domain: &RetainedParameterDomain1,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedTrimInterval1>> {
RetainedTrimInterval1::try_new(domain.start().clone(), domain.end().clone(), domain, policy)
}
fn endpoint_evidence(
control_points: &[Point2],
domain: &RetainedParameterDomain1,
) -> CurveResult<RetainedEndpointEvidence2> {
let start_point = control_points
.first()
.ok_or(CurveError::InvalidBSpline)?
.clone();
let end_point = control_points
.last()
.ok_or(CurveError::InvalidBSpline)?
.clone();
Ok(RetainedEndpointEvidence2::new(
domain,
start_point,
end_point,
))
}
fn distinct_interior_knots(
knots: &[Real],
degree: usize,
policy: &CurvePolicy,
) -> Classification<Vec<Real>> {
let mut result = Vec::new();
for knot in &knots[degree + 1..knots.len() - degree - 1] {
if result
.last()
.is_some_and(|last| compare_reals(last, knot, policy) == Some(Ordering::Equal))
{
continue;
}
result.push(knot.clone());
}
Classification::Decided(result)
}
fn knot_multiplicity(knots: &[Real], knot: &Real, policy: &CurvePolicy) -> CurveResult<usize> {
let mut count = 0;
for candidate in knots {
match compare_reals(candidate, knot, policy) {
Some(Ordering::Equal) => count += 1,
Some(Ordering::Less | Ordering::Greater) => {}
None => return Err(CurveError::InvalidBSpline),
}
}
Ok(count)
}
fn weights_are_all_equal(weights: &[Real], policy: &CurvePolicy) -> Classification<bool> {
let Some(first) = weights.first() else {
return Classification::Uncertain(UncertaintyReason::Unsupported);
};
for weight in &weights[1..] {
match compare_reals(first, weight, policy) {
Some(Ordering::Equal) => {}
Some(Ordering::Less | Ordering::Greater) => return Classification::Decided(false),
None => return Classification::Uncertain(UncertaintyReason::Ordering),
}
}
Classification::Decided(true)
}
fn find_insertion_span(
knots: &[Real],
degree: usize,
control_count: usize,
knot: &Real,
policy: &CurvePolicy,
) -> CurveResult<Option<usize>> {
let n = control_count - 1;
if compare_reals(knot, &knots[n + 1], policy) == Some(Ordering::Equal) {
return Ok(Some(n));
}
for span in degree..=n {
let left = compare_reals(&knots[span], knot, policy);
let right = compare_reals(knot, &knots[span + 1], policy);
match (left, right) {
(Some(Ordering::Less | Ordering::Equal), Some(Ordering::Less)) => {
return Ok(Some(span));
}
(Some(_), Some(_)) => {}
_ => return Ok(None),
}
}
Ok(None)
}
fn extract_refined_bezier_spans(
refined: &BSplineWorkingCurve,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<BezierSubcurve2>>> {
let mut spans = Vec::new();
for knot_index in refined.degree..refined.control_points.len() {
if compare_reals(
&refined.knots[knot_index],
&refined.knots[knot_index + 1],
policy,
) != Some(Ordering::Less)
{
continue;
}
let start = knot_index - refined.degree;
let controls = &refined.control_points[start..=knot_index];
let span = match refined.degree {
2 => BezierSubcurve2::Quadratic(QuadraticBezier2::new(
controls[0].clone(),
controls[1].clone(),
controls[2].clone(),
)),
3 => BezierSubcurve2::Cubic(CubicBezier2::new(
controls[0].clone(),
controls[1].clone(),
controls[2].clone(),
controls[3].clone(),
)),
_ => return Ok(Classification::Uncertain(UncertaintyReason::Unsupported)),
};
spans.push(span);
}
Ok(Classification::Decided(spans))
}
fn extract_refined_rational_quadratic_spans(
refined: &HomogeneousBSplineWorkingCurve,
policy: &CurvePolicy,
) -> CurveResult<Classification<RationalQuadraticBSplineBezierExtraction2>> {
let mut affine_controls = Vec::with_capacity(refined.controls.len());
let mut weights = Vec::with_capacity(refined.controls.len());
for control in &refined.controls {
match control.to_affine(policy)? {
Classification::Decided((point, weight)) => {
affine_controls.push(point);
weights.push(weight);
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
let mut spans = Vec::new();
for knot_index in refined.degree..refined.controls.len() {
if compare_reals(
&refined.knots[knot_index],
&refined.knots[knot_index + 1],
policy,
) != Some(Ordering::Less)
{
continue;
}
let start = knot_index - refined.degree;
let curve = RationalQuadraticBezier2::try_new(
affine_controls[start].clone(),
affine_controls[start + 1].clone(),
affine_controls[start + 2].clone(),
weights[start].clone(),
weights[start + 1].clone(),
weights[start + 2].clone(),
)?;
spans.push(BezierSubcurve2::RationalQuadratic(curve));
}
Ok(Classification::Decided(
RationalQuadraticBSplineBezierExtraction2 {
refined_control_points: affine_controls,
refined_weights: weights,
refined_knots: refined.knots.clone(),
spans,
inserted_knot_count: refined.inserted_knot_count,
},
))
}
fn extract_refined_rational_spans(
refined: &HomogeneousBSplineWorkingCurve,
policy: &CurvePolicy,
) -> CurveResult<Classification<RationalBSplineBezierExtraction2>> {
let (affine_controls, weights) = match refined_affine_controls(refined, policy)? {
Classification::Decided(refined) => refined,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let mut spans = Vec::new();
for knot_index in refined.degree..refined.controls.len() {
if compare_reals(
&refined.knots[knot_index],
&refined.knots[knot_index + 1],
policy,
) != Some(Ordering::Less)
{
continue;
}
let start = knot_index - refined.degree;
spans.push(RationalBezierSpan2 {
degree: refined.degree,
control_points: affine_controls[start..=knot_index].to_vec(),
weights: weights[start..=knot_index].to_vec(),
knot_start: refined.knots[knot_index].clone(),
knot_end: refined.knots[knot_index + 1].clone(),
});
}
Ok(Classification::Decided(RationalBSplineBezierExtraction2 {
degree: refined.degree,
refined_control_points: affine_controls,
refined_weights: weights,
refined_knots: refined.knots.clone(),
spans,
inserted_knot_count: refined.inserted_knot_count,
}))
}
fn refined_affine_controls(
refined: &HomogeneousBSplineWorkingCurve,
policy: &CurvePolicy,
) -> CurveResult<Classification<(Vec<Point2>, Vec<Real>)>> {
let mut affine_controls = Vec::with_capacity(refined.controls.len());
let mut weights = Vec::with_capacity(refined.controls.len());
for control in &refined.controls {
match control.to_affine(policy)? {
Classification::Decided((point, weight)) => {
affine_controls.push(point);
weights.push(weight);
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
Ok(Classification::Decided((affine_controls, weights)))
}