use hyperreal::Real;
use crate::classify::compare_reals;
use crate::{
Classification, CurveError, CurvePolicy, CurveResult, Point2, RetainedTopologyStatus,
UncertaintyReason,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetainedCurveFamily2 {
PolynomialBSpline,
RationalQuadraticBSpline,
RationalBSpline,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RetainedCurveIdentity2 {
family: RetainedCurveFamily2,
source_index: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedParameterDomain1 {
start: Real,
end: Real,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetainedTrimDirection {
Forward,
Reversed,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedTrimInterval1 {
start: Real,
end: Real,
direction: RetainedTrimDirection,
}
#[derive(Clone, Debug, PartialEq)]
pub enum RetainedCurvePeriodicity1 {
NonPeriodic,
Periodic { period: Box<Real> },
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedEndpointEvidence2 {
start_parameter: Real,
end_parameter: Real,
start_point: Point2,
end_point: Point2,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RetainedCurveCacheSummary2 {
control_count: usize,
knot_count: usize,
span_count: usize,
native_span_count: usize,
retained_span_count: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedCurveProfile2 {
identity: RetainedCurveIdentity2,
domain: RetainedParameterDomain1,
trim: RetainedTrimInterval1,
periodicity: RetainedCurvePeriodicity1,
topology_status: RetainedTopologyStatus,
endpoints: RetainedEndpointEvidence2,
cache_summary: RetainedCurveCacheSummary2,
}
impl RetainedCurveIdentity2 {
pub const fn new(family: RetainedCurveFamily2, source_index: u64) -> Self {
Self {
family,
source_index,
}
}
pub const fn family(self) -> RetainedCurveFamily2 {
self.family
}
pub const fn source_index(self) -> u64 {
self.source_index
}
}
impl RetainedParameterDomain1 {
pub fn try_new(
start: Real,
end: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
match compare_reals(&start, &end, policy) {
Some(std::cmp::Ordering::Less) => Ok(Classification::Decided(Self { start, end })),
Some(std::cmp::Ordering::Equal | std::cmp::Ordering::Greater) => {
Err(CurveError::InvalidBezierRange)
}
None => Ok(Classification::Uncertain(UncertaintyReason::Ordering)),
}
}
pub const fn start(&self) -> &Real {
&self.start
}
pub const fn end(&self) -> &Real {
&self.end
}
pub fn contains(&self, parameter: &Real, policy: &CurvePolicy) -> Classification<bool> {
let lower = compare_reals(&self.start, parameter, policy);
let upper = compare_reals(parameter, &self.end, policy);
match (lower, upper) {
(
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal),
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal),
) => Classification::Decided(true),
(Some(_), Some(_)) => Classification::Decided(false),
_ => Classification::Uncertain(UncertaintyReason::Ordering),
}
}
}
impl RetainedTrimInterval1 {
pub fn try_new(
start: Real,
end: Real,
domain: &RetainedParameterDomain1,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
for parameter in [&start, &end] {
match domain.contains(parameter, policy) {
Classification::Decided(true) => {}
Classification::Decided(false) => return Err(CurveError::InvalidBezierRange),
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
}
}
let direction = match compare_reals(&start, &end, policy) {
Some(std::cmp::Ordering::Less) => RetainedTrimDirection::Forward,
Some(std::cmp::Ordering::Greater) => RetainedTrimDirection::Reversed,
Some(std::cmp::Ordering::Equal) => return Err(CurveError::InvalidBezierRange),
None => return Ok(Classification::Uncertain(UncertaintyReason::Ordering)),
};
Ok(Classification::Decided(Self {
start,
end,
direction,
}))
}
pub const fn start(&self) -> &Real {
&self.start
}
pub const fn end(&self) -> &Real {
&self.end
}
pub const fn direction(&self) -> RetainedTrimDirection {
self.direction
}
}
impl RetainedCurvePeriodicity1 {
pub fn periodic(period: Real, policy: &CurvePolicy) -> CurveResult<Classification<Self>> {
match compare_reals(&Real::zero(), &period, policy) {
Some(std::cmp::Ordering::Less) => Ok(Classification::Decided(Self::Periodic {
period: Box::new(period),
})),
Some(_) => Err(CurveError::InvalidBezierRange),
None => Ok(Classification::Uncertain(UncertaintyReason::Ordering)),
}
}
}
impl RetainedEndpointEvidence2 {
pub fn new(domain: &RetainedParameterDomain1, start_point: Point2, end_point: Point2) -> Self {
Self {
start_parameter: domain.start().clone(),
end_parameter: domain.end().clone(),
start_point,
end_point,
}
}
pub const fn start_parameter(&self) -> &Real {
&self.start_parameter
}
pub const fn end_parameter(&self) -> &Real {
&self.end_parameter
}
pub const fn start_point(&self) -> &Point2 {
&self.start_point
}
pub const fn end_point(&self) -> &Point2 {
&self.end_point
}
}
impl RetainedCurveCacheSummary2 {
pub fn new(
control_count: usize,
knot_count: usize,
span_count: usize,
native_span_count: usize,
retained_span_count: usize,
) -> CurveResult<Self> {
validate_cache_summary_counts(
control_count,
knot_count,
span_count,
native_span_count,
retained_span_count,
)?;
Ok(Self {
control_count,
knot_count,
span_count,
native_span_count,
retained_span_count,
})
}
pub const fn control_count(&self) -> usize {
self.control_count
}
pub const fn knot_count(&self) -> usize {
self.knot_count
}
pub const fn span_count(&self) -> usize {
self.span_count
}
pub const fn native_span_count(&self) -> usize {
self.native_span_count
}
pub const fn retained_span_count(&self) -> usize {
self.retained_span_count
}
}
impl RetainedCurveProfile2 {
pub fn new(
identity: RetainedCurveIdentity2,
domain: RetainedParameterDomain1,
trim: RetainedTrimInterval1,
periodicity: RetainedCurvePeriodicity1,
topology_status: RetainedTopologyStatus,
endpoints: RetainedEndpointEvidence2,
cache_summary: RetainedCurveCacheSummary2,
) -> CurveResult<Self> {
validate_curve_profile_evidence(
identity,
&domain,
&trim,
topology_status,
&endpoints,
&cache_summary,
)?;
Ok(Self {
identity,
domain,
trim,
periodicity,
topology_status,
endpoints,
cache_summary,
})
}
pub const fn identity(&self) -> RetainedCurveIdentity2 {
self.identity
}
pub const fn domain(&self) -> &RetainedParameterDomain1 {
&self.domain
}
pub const fn trim(&self) -> &RetainedTrimInterval1 {
&self.trim
}
pub const fn periodicity(&self) -> &RetainedCurvePeriodicity1 {
&self.periodicity
}
pub const fn topology_status(&self) -> RetainedTopologyStatus {
self.topology_status
}
pub const fn endpoints(&self) -> &RetainedEndpointEvidence2 {
&self.endpoints
}
pub const fn cache_summary(&self) -> &RetainedCurveCacheSummary2 {
&self.cache_summary
}
}
fn validate_cache_summary_counts(
control_count: usize,
knot_count: usize,
span_count: usize,
native_span_count: usize,
retained_span_count: usize,
) -> CurveResult<()> {
if control_count == 0 || knot_count == 0 || span_count == 0 {
return Err(CurveError::Topology(
"retained curve cache summary must carry nonempty controls, knots, and spans".into(),
));
}
if knot_count <= control_count {
return Err(CurveError::Topology(
"retained B-spline cache summary must carry more knots than controls".into(),
));
}
if span_count
.checked_add(2)
.is_none_or(|minimum_control_count| control_count < minimum_control_count)
{
return Err(CurveError::Topology(
"retained B-spline cache summary must carry at least two more controls than spans"
.into(),
));
}
let Some(order) = knot_count.checked_sub(control_count) else {
return Err(CurveError::Topology(
"retained B-spline cache summary knot/control counts are inconsistent".into(),
));
};
if order < 3 || control_count < order {
return Err(CurveError::Topology(
"retained B-spline cache summary must carry a supported degree shape".into(),
));
}
let degree = order - 1;
if span_count > control_count - degree {
return Err(CurveError::Topology(
"retained B-spline cache summary span count exceeds the degree-implied maximum".into(),
));
}
if native_span_count
.checked_add(retained_span_count)
.is_none_or(|count| count != span_count)
{
return Err(CurveError::Topology(
"retained curve cache summary span decomposition does not match span count".into(),
));
}
Ok(())
}
fn validate_curve_profile_evidence(
identity: RetainedCurveIdentity2,
domain: &RetainedParameterDomain1,
trim: &RetainedTrimInterval1,
topology_status: RetainedTopologyStatus,
endpoints: &RetainedEndpointEvidence2,
cache_summary: &RetainedCurveCacheSummary2,
) -> CurveResult<()> {
validate_profile_family_shape(identity, cache_summary)?;
let policy = CurvePolicy::certified();
for parameter in [trim.start(), trim.end()] {
if domain.contains(parameter, &policy) != Classification::Decided(true) {
return Err(CurveError::Topology(
"retained curve trim evidence must lie inside the active parameter domain".into(),
));
}
}
if endpoints.start_parameter() != domain.start() || endpoints.end_parameter() != domain.end() {
return Err(CurveError::Topology(
"retained curve endpoint evidence must match the active parameter domain".into(),
));
}
if topology_status.is_native_exact() && cache_summary.retained_span_count() != 0 {
return Err(CurveError::Topology(
"native retained curve profile must not report retained unsupported spans".into(),
));
}
if !topology_status.is_native_exact() && cache_summary.retained_span_count() == 0 {
return Err(CurveError::Topology(
"non-native retained curve profile must report retained evidence spans".into(),
));
}
if !topology_status.is_native_exact() && !topology_status.is_retained_evidence() {
return Err(CurveError::Topology(
"retained curve profile must carry exact native or retained evidence status".into(),
));
}
Ok(())
}
fn validate_profile_family_shape(
identity: RetainedCurveIdentity2,
cache_summary: &RetainedCurveCacheSummary2,
) -> CurveResult<()> {
let degree = cache_summary
.knot_count()
.checked_sub(cache_summary.control_count())
.and_then(|order| order.checked_sub(1))
.ok_or_else(|| {
CurveError::Topology(
"retained curve profile cache summary has no certified B-spline degree".into(),
)
})?;
match identity.family() {
RetainedCurveFamily2::PolynomialBSpline if !(2..=3).contains(°ree) => {
Err(CurveError::Topology(
"polynomial B-spline profile must carry quadratic or cubic cache evidence".into(),
))
}
RetainedCurveFamily2::RationalQuadraticBSpline if degree != 2 => Err(CurveError::Topology(
"rational quadratic B-spline profile must carry quadratic cache evidence".into(),
)),
_ => Ok(()),
}
}