use hyperreal::{Real, RealSign};
use crate::classify::{compare_reals, is_zero, real_sign};
use crate::{
Aabb2, BezierArrangementGraph2, BezierArrangementTraversal2, BezierEndpointPointImage2,
BezierLineContactKind, BezierLineContactRelation, BezierLineImageFitRelation, BezierParameter2,
BezierRetainedLinearOverlapTraversal2, BezierSplitFragment2, BezierSubcurve2, Classification,
Contour2, ContourPointLocation, CurveError, CurvePolicy, CurveResult, LineSeg2, Point2,
Region2, Segment2, UncertaintyReason,
};
#[derive(Clone, Debug, PartialEq)]
pub struct BezierBoundaryLoop2 {
fragments: Vec<BezierSubcurve2>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BezierRegion2 {
boundary_loops: Vec<BezierBoundaryLoop2>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierRetainedBoundaryLoop2 {
fragments: Vec<BezierSplitFragment2>,
arrangement_sources: Option<Vec<BezierRetainedFragmentSource2>>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BezierRetainedFragmentSource2 {
arrangement_fragment_index: usize,
source_curve_index: usize,
source_fragment_index: usize,
}
impl BezierRetainedFragmentSource2 {
pub const fn new(
arrangement_fragment_index: usize,
source_curve_index: usize,
source_fragment_index: usize,
) -> Self {
Self {
arrangement_fragment_index,
source_curve_index,
source_fragment_index,
}
}
pub const fn arrangement_fragment_index(self) -> usize {
self.arrangement_fragment_index
}
pub const fn source_curve_index(self) -> usize {
self.source_curve_index
}
pub const fn source_fragment_index(self) -> usize {
self.source_fragment_index
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BezierRetainedRegion2 {
boundary_loops: Vec<BezierRetainedBoundaryLoop2>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BezierRetainedRegionLoopRole {
Material,
Hole,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierRetainedLineRegionRoleReport2 {
roles: Vec<BezierRetainedRegionLoopRole>,
nesting_depths: Vec<usize>,
materialized_fragment_count: usize,
algebraic_fragment_count: usize,
contours: Vec<Contour2>,
loop_arrangement_sources: Option<Vec<Option<Vec<BezierRetainedFragmentSource2>>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierRetainedSignedAreaRoleReport2 {
roles: Vec<BezierRetainedRegionLoopRole>,
signed_areas: Vec<Real>,
loop_fragment_counts: Option<Vec<usize>>,
loop_arrangement_sources: Option<Vec<Option<Vec<BezierRetainedFragmentSource2>>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierRetainedCurvedNestingRoleReport2 {
roles: Vec<BezierRetainedRegionLoopRole>,
nesting_depths: Vec<usize>,
signed_areas: Vec<Real>,
sample_points: Vec<Point2>,
loop_fragment_counts: Option<Vec<usize>>,
loop_arrangement_sources: Option<Vec<Option<Vec<BezierRetainedFragmentSource2>>>>,
}
impl BezierRetainedLineRegionRoleReport2 {
pub fn new(
roles: Vec<BezierRetainedRegionLoopRole>,
nesting_depths: Vec<usize>,
materialized_fragment_count: usize,
algebraic_fragment_count: usize,
contours: Vec<Contour2>,
) -> CurveResult<Self> {
validate_report_length(roles.len(), "nesting depth", nesting_depths.len())?;
validate_report_length(roles.len(), "line contour", contours.len())?;
validate_nesting_depth_roles(&roles, &nesting_depths)?;
validate_line_role_report_fragment_counts(
materialized_fragment_count,
algebraic_fragment_count,
&contours,
)?;
Ok(Self {
roles,
nesting_depths,
materialized_fragment_count,
algebraic_fragment_count,
contours,
loop_arrangement_sources: None,
})
}
pub fn with_loop_arrangement_sources(
mut self,
loop_arrangement_sources: Vec<Option<Vec<BezierRetainedFragmentSource2>>>,
) -> CurveResult<Self> {
validate_loop_arrangement_sources(self.roles.len(), &loop_arrangement_sources)?;
validate_line_loop_arrangement_source_counts(&self.contours, &loop_arrangement_sources)?;
self.loop_arrangement_sources = Some(loop_arrangement_sources);
Ok(self)
}
pub fn roles(&self) -> &[BezierRetainedRegionLoopRole] {
&self.roles
}
pub fn nesting_depths(&self) -> &[usize] {
&self.nesting_depths
}
pub const fn materialized_fragment_count(&self) -> usize {
self.materialized_fragment_count
}
pub const fn algebraic_fragment_count(&self) -> usize {
self.algebraic_fragment_count
}
pub const fn has_algebraic_fragments(&self) -> bool {
self.algebraic_fragment_count > 0
}
pub fn contours(&self) -> &[Contour2] {
&self.contours
}
pub fn loop_arrangement_sources(
&self,
) -> Option<&[Option<Vec<BezierRetainedFragmentSource2>>]> {
self.loop_arrangement_sources.as_deref()
}
pub fn material_loop_indices(&self) -> Vec<usize> {
self.roles
.iter()
.enumerate()
.filter_map(|(index, role)| {
(*role == BezierRetainedRegionLoopRole::Material).then_some(index)
})
.collect()
}
pub fn hole_loop_indices(&self) -> Vec<usize> {
self.roles
.iter()
.enumerate()
.filter_map(|(index, role)| {
(*role == BezierRetainedRegionLoopRole::Hole).then_some(index)
})
.collect()
}
pub fn to_region(&self) -> Region2 {
let mut material = Vec::new();
let mut holes = Vec::new();
for (contour, role) in self
.contours
.iter()
.cloned()
.zip(self.roles.iter().copied())
{
match role {
BezierRetainedRegionLoopRole::Material => material.push(contour),
BezierRetainedRegionLoopRole::Hole => holes.push(contour),
}
}
Region2::new(material, holes)
}
}
impl BezierRetainedSignedAreaRoleReport2 {
pub fn new(
roles: Vec<BezierRetainedRegionLoopRole>,
signed_areas: Vec<Real>,
) -> CurveResult<Self> {
validate_report_length(roles.len(), "signed area", signed_areas.len())?;
validate_signed_area_roles(&roles, &signed_areas)?;
Ok(Self {
roles,
signed_areas,
loop_fragment_counts: None,
loop_arrangement_sources: None,
})
}
fn with_loop_fragment_counts(mut self, loop_fragment_counts: Vec<usize>) -> CurveResult<Self> {
validate_loop_fragment_counts(self.roles.len(), &loop_fragment_counts)?;
self.loop_fragment_counts = Some(loop_fragment_counts);
Ok(self)
}
pub fn with_loop_arrangement_sources(
mut self,
loop_arrangement_sources: Vec<Option<Vec<BezierRetainedFragmentSource2>>>,
) -> CurveResult<Self> {
validate_loop_arrangement_sources(self.roles.len(), &loop_arrangement_sources)?;
validate_counted_loop_arrangement_source_counts(
self.loop_fragment_counts.as_deref(),
&loop_arrangement_sources,
)?;
self.loop_arrangement_sources = Some(loop_arrangement_sources);
Ok(self)
}
pub fn roles(&self) -> &[BezierRetainedRegionLoopRole] {
&self.roles
}
pub fn signed_areas(&self) -> &[Real] {
&self.signed_areas
}
pub fn loop_arrangement_sources(
&self,
) -> Option<&[Option<Vec<BezierRetainedFragmentSource2>>]> {
self.loop_arrangement_sources.as_deref()
}
pub fn material_loop_indices(&self) -> Vec<usize> {
self.roles
.iter()
.enumerate()
.filter_map(|(index, role)| {
(*role == BezierRetainedRegionLoopRole::Material).then_some(index)
})
.collect()
}
pub fn hole_loop_indices(&self) -> Vec<usize> {
self.roles
.iter()
.enumerate()
.filter_map(|(index, role)| {
(*role == BezierRetainedRegionLoopRole::Hole).then_some(index)
})
.collect()
}
}
impl BezierRetainedCurvedNestingRoleReport2 {
pub fn new(
roles: Vec<BezierRetainedRegionLoopRole>,
nesting_depths: Vec<usize>,
signed_areas: Vec<Real>,
sample_points: Vec<Point2>,
) -> CurveResult<Self> {
validate_report_length(roles.len(), "nesting depth", nesting_depths.len())?;
validate_report_length(roles.len(), "signed area", signed_areas.len())?;
validate_report_length(roles.len(), "sample point", sample_points.len())?;
validate_nesting_depth_roles(&roles, &nesting_depths)?;
validate_nonzero_signed_area_evidence(&signed_areas)?;
Ok(Self {
roles,
nesting_depths,
signed_areas,
sample_points,
loop_fragment_counts: None,
loop_arrangement_sources: None,
})
}
fn with_loop_fragment_counts(mut self, loop_fragment_counts: Vec<usize>) -> CurveResult<Self> {
validate_loop_fragment_counts(self.roles.len(), &loop_fragment_counts)?;
self.loop_fragment_counts = Some(loop_fragment_counts);
Ok(self)
}
pub fn with_loop_arrangement_sources(
mut self,
loop_arrangement_sources: Vec<Option<Vec<BezierRetainedFragmentSource2>>>,
) -> CurveResult<Self> {
validate_loop_arrangement_sources(self.roles.len(), &loop_arrangement_sources)?;
validate_counted_loop_arrangement_source_counts(
self.loop_fragment_counts.as_deref(),
&loop_arrangement_sources,
)?;
self.loop_arrangement_sources = Some(loop_arrangement_sources);
Ok(self)
}
pub fn roles(&self) -> &[BezierRetainedRegionLoopRole] {
&self.roles
}
pub fn nesting_depths(&self) -> &[usize] {
&self.nesting_depths
}
pub fn signed_areas(&self) -> &[Real] {
&self.signed_areas
}
pub fn sample_points(&self) -> &[Point2] {
&self.sample_points
}
pub fn loop_arrangement_sources(
&self,
) -> Option<&[Option<Vec<BezierRetainedFragmentSource2>>]> {
self.loop_arrangement_sources.as_deref()
}
pub fn material_loop_indices(&self) -> Vec<usize> {
self.roles
.iter()
.enumerate()
.filter_map(|(index, role)| {
(*role == BezierRetainedRegionLoopRole::Material).then_some(index)
})
.collect()
}
pub fn hole_loop_indices(&self) -> Vec<usize> {
self.roles
.iter()
.enumerate()
.filter_map(|(index, role)| {
(*role == BezierRetainedRegionLoopRole::Hole).then_some(index)
})
.collect()
}
}
impl BezierBoundaryLoop2 {
pub fn new(fragments: Vec<BezierSubcurve2>) -> CurveResult<Self> {
validate_native_boundary_loop(&fragments)?;
Ok(Self { fragments })
}
pub fn fragments(&self) -> &[BezierSubcurve2] {
&self.fragments
}
pub fn into_fragments(self) -> Vec<BezierSubcurve2> {
self.fragments
}
pub fn len(&self) -> usize {
self.fragments.len()
}
pub fn is_empty(&self) -> bool {
self.fragments.is_empty()
}
pub fn signed_area(&self) -> CurveResult<Option<Real>> {
if self.fragments.is_empty() {
return Err(CurveError::Topology(
"Bezier boundary loop signed area requires nonempty fragments".to_owned(),
));
}
let mut total = Real::zero();
for fragment in &self.fragments {
let Some(contribution) = fragment.signed_area_contribution()? else {
return Ok(None);
};
total = &total + &contribution;
}
Ok(Some(total))
}
}
impl BezierRegion2 {
pub fn new(boundary_loops: Vec<BezierBoundaryLoop2>) -> CurveResult<Self> {
validate_bezier_region_loops(&boundary_loops)?;
Ok(Self { boundary_loops })
}
pub fn from_arrangement_traversal(
graph: &BezierArrangementGraph2,
traversal: &BezierArrangementTraversal2,
) -> Classification<Self> {
let mut loops = Vec::with_capacity(traversal.chains().len());
for chain in traversal.chains() {
if !chain.is_closed() {
return Classification::Uncertain(UncertaintyReason::Boundary);
}
let mut fragments = Vec::with_capacity(chain.len());
for index in chain.fragment_indices() {
let Some(fragment) = graph.fragments().get(*index) else {
return Classification::Uncertain(UncertaintyReason::Unsupported);
};
match fragment.fragment() {
BezierSplitFragment2::Materialized { curve, .. } => {
fragments.push(curve.clone());
}
BezierSplitFragment2::AlgebraicEndpointImages { .. }
| BezierSplitFragment2::Unresolved { .. } => {
return Classification::Uncertain(UncertaintyReason::Boundary);
}
}
}
let loop_ = match BezierBoundaryLoop2::new(fragments) {
Ok(loop_) => loop_,
Err(_) => return Classification::Uncertain(UncertaintyReason::Unsupported),
};
loops.push(loop_);
}
match Self::new(loops) {
Ok(region) => Classification::Decided(region),
Err(_) => Classification::Uncertain(UncertaintyReason::Unsupported),
}
}
pub fn from_retained_linear_overlap_traversal(
traversal: &BezierRetainedLinearOverlapTraversal2,
) -> Classification<Self> {
Self::from_arrangement_traversal(traversal.refinement().graph(), traversal.traversal())
}
pub fn boundary_loops(&self) -> &[BezierBoundaryLoop2] {
&self.boundary_loops
}
pub fn into_boundary_loops(self) -> Vec<BezierBoundaryLoop2> {
self.boundary_loops
}
pub fn is_empty(&self) -> bool {
self.boundary_loops.is_empty()
}
pub fn len(&self) -> usize {
self.boundary_loops.len()
}
pub fn signed_area(&self) -> CurveResult<Option<Real>> {
let mut total = Real::zero();
for boundary_loop in &self.boundary_loops {
let Some(area) = boundary_loop.signed_area()? else {
return Ok(None);
};
total = &total + &area;
}
Ok(Some(total))
}
}
fn validate_native_boundary_loop(fragments: &[BezierSubcurve2]) -> CurveResult<()> {
if fragments.is_empty() {
return Err(CurveError::Topology(
"Bezier boundary loop requires nonempty fragments".to_owned(),
));
}
let policy = CurvePolicy::certified();
for (left, right) in fragments
.iter()
.zip(fragments.iter().cycle().skip(1))
.take(fragments.len())
{
if !certified_points_equal(&left.endpoints().1, &right.endpoints().0, &policy) {
return Err(CurveError::Topology(
"Bezier boundary loop fragments must be endpoint-connected and closed".to_owned(),
));
}
}
Ok(())
}
fn certified_points_equal(left: &Point2, right: &Point2, policy: &CurvePolicy) -> bool {
is_zero(&left.distance_squared(right), policy) == Some(true)
}
fn validate_bezier_region_loops<Loop>(boundary_loops: &[Loop]) -> CurveResult<()>
where
Loop: PartialEq,
{
for (index, boundary_loop) in boundary_loops.iter().enumerate() {
if boundary_loops[index + 1..].contains(boundary_loop) {
return Err(CurveError::Topology(
"Bezier region must not duplicate boundary loop evidence".to_owned(),
));
}
}
Ok(())
}
impl BezierRetainedBoundaryLoop2 {
pub fn new(fragments: Vec<BezierSplitFragment2>) -> CurveResult<Self> {
validate_retained_boundary_loop(&fragments)?;
Ok(Self {
fragments,
arrangement_sources: None,
})
}
pub fn try_new_with_arrangement_sources(
fragments: Vec<BezierSplitFragment2>,
arrangement_sources: Vec<BezierRetainedFragmentSource2>,
) -> CurveResult<Self> {
validate_retained_boundary_loop(&fragments)?;
if fragments.len() != arrangement_sources.len() {
return Err(CurveError::Topology(
"retained boundary source count does not match fragment count".to_owned(),
));
}
validate_retained_boundary_loop_sources(&arrangement_sources)?;
Ok(Self {
fragments,
arrangement_sources: Some(arrangement_sources),
})
}
pub fn fragments(&self) -> &[BezierSplitFragment2] {
&self.fragments
}
pub fn into_fragments(self) -> Vec<BezierSplitFragment2> {
self.fragments
}
pub fn arrangement_sources(&self) -> Option<&[BezierRetainedFragmentSource2]> {
self.arrangement_sources.as_deref()
}
pub const fn has_arrangement_sources(&self) -> bool {
self.arrangement_sources.is_some()
}
pub fn len(&self) -> usize {
self.fragments.len()
}
pub fn is_empty(&self) -> bool {
self.fragments.is_empty()
}
pub fn has_algebraic_fragments(&self) -> bool {
self.fragments.iter().any(|fragment| {
matches!(
fragment,
BezierSplitFragment2::AlgebraicEndpointImages { .. }
)
})
}
pub fn signed_area(&self) -> CurveResult<Option<Real>> {
if self.fragments.is_empty() {
return Err(CurveError::Topology(
"retained Bezier boundary loop signed area requires nonempty fragments".to_owned(),
));
}
let mut total = Real::zero();
for fragment in &self.fragments {
let BezierSplitFragment2::Materialized { curve, .. } = fragment else {
return Ok(None);
};
let Some(contribution) = curve.signed_area_contribution()? else {
return Ok(None);
};
total = &total + &contribution;
}
Ok(Some(total))
}
}
fn validate_retained_boundary_loop(fragments: &[BezierSplitFragment2]) -> CurveResult<()> {
if fragments.is_empty() {
return Err(CurveError::Topology(
"retained Bezier boundary loop requires nonempty fragments".to_owned(),
));
}
for fragment in fragments {
validate_retained_fragment_provenance(fragment, &CurvePolicy::certified())?;
}
validate_retained_boundary_loop_connectivity(fragments, &CurvePolicy::certified())
}
fn validate_retained_fragment_provenance(
fragment: &BezierSplitFragment2,
policy: &CurvePolicy,
) -> CurveResult<()> {
match fragment {
BezierSplitFragment2::Materialized { start, end, .. } => {
if !start.is_exact() || !end.is_exact() {
return Err(CurveError::Topology(
"retained materialized Bezier fragment must carry exact range boundaries"
.into(),
));
}
validate_retained_fragment_parameter_order(start, end, policy)
}
BezierSplitFragment2::AlgebraicEndpointImages {
start,
end,
source_curve,
start_image,
end_image,
} => {
validate_retained_source_endpoint_image(
start,
source_curve,
start_image.as_ref(),
policy,
)?;
validate_retained_source_endpoint_image(end, source_curve, end_image.as_ref(), policy)
}
BezierSplitFragment2::Unresolved { .. } => Err(CurveError::Topology(
"retained Bezier region boundary loops must not contain unresolved carriers".into(),
)),
}
}
fn validate_retained_fragment_parameter_order(
start: &BezierParameter2,
end: &BezierParameter2,
policy: &CurvePolicy,
) -> CurveResult<()> {
match start.cmp_by_interval(end, policy)? {
Classification::Decided(std::cmp::Ordering::Less) => Ok(()),
Classification::Decided(std::cmp::Ordering::Equal | std::cmp::Ordering::Greater) => {
Err(CurveError::Topology(
"retained Bezier fragment range must be certified strictly increasing".into(),
))
}
Classification::Uncertain(reason) => Err(CurveError::Topology(format!(
"retained Bezier fragment range ordering is uncertain: {reason:?}"
))),
}
}
fn validate_retained_source_endpoint_image(
boundary: &BezierParameter2,
source_curve: &Option<BezierSubcurve2>,
image: Option<&crate::BezierAlgebraicEndpointImage2>,
policy: &CurvePolicy,
) -> CurveResult<()> {
match boundary {
BezierParameter2::Exact(_) => {
if image.is_some() {
return Err(CurveError::Topology(
"retained exact endpoint must not carry algebraic endpoint image evidence"
.into(),
));
}
}
BezierParameter2::Algebraic(parameter) => {
let Some(image) = image else {
return Err(CurveError::Topology(
"retained algebraic boundary must carry endpoint image evidence".into(),
));
};
if image.parameter() != parameter {
return Err(CurveError::Topology(
"retained algebraic endpoint image parameter does not match boundary".into(),
));
}
if !image.is_transformed() {
return Err(CurveError::Topology(
"retained algebraic endpoint image must be exact transformed evidence".into(),
));
}
if let Some(source_curve) = source_curve {
let expected = crate::BezierAlgebraicEndpointImage2::from_source_curve(
source_curve,
parameter,
policy,
)?;
if &expected != image {
return Err(CurveError::Topology(
"retained algebraic endpoint image does not match retained source curve"
.into(),
));
}
}
}
}
Ok(())
}
fn validate_retained_boundary_loop_sources(
arrangement_sources: &[BezierRetainedFragmentSource2],
) -> CurveResult<()> {
let mut indices = arrangement_sources
.iter()
.map(|source| source.arrangement_fragment_index())
.collect::<Vec<_>>();
indices.sort_unstable();
if indices.windows(2).any(|window| window[0] == window[1]) {
return Err(CurveError::Topology(
"retained boundary loop source provenance must not reuse arrangement fragments"
.to_owned(),
));
}
Ok(())
}
fn validate_retained_region_loops(
boundary_loops: &[BezierRetainedBoundaryLoop2],
) -> CurveResult<()> {
validate_bezier_region_loops(boundary_loops)?;
validate_retained_region_arrangement_sources(boundary_loops)
}
fn validate_retained_region_arrangement_sources(
boundary_loops: &[BezierRetainedBoundaryLoop2],
) -> CurveResult<()> {
let mut indices = Vec::new();
for boundary_loop in boundary_loops {
if let Some(sources) = boundary_loop.arrangement_sources() {
indices.extend(
sources
.iter()
.map(|source| source.arrangement_fragment_index()),
);
}
}
validate_unique_arrangement_source_indices(
indices,
"retained Bezier region boundary loops must not reuse arrangement source fragments",
)
}
#[derive(Clone, Debug, PartialEq)]
struct RetainedEndpointEvidence {
point: Option<Point2>,
source: Option<(BezierSubcurve2, BezierParameter2)>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RetainedEndpointEquality {
Equal,
NotEqual,
Uncertified,
}
fn validate_retained_boundary_loop_connectivity(
fragments: &[BezierSplitFragment2],
policy: &CurvePolicy,
) -> CurveResult<()> {
for (left, right) in fragments
.iter()
.zip(fragments.iter().cycle().skip(1))
.take(fragments.len())
{
let left_end = retained_fragment_endpoint_evidence(left, false, policy)?;
let right_start = retained_fragment_endpoint_evidence(right, true, policy)?;
match retained_endpoint_equality(&left_end, &right_start, policy) {
RetainedEndpointEquality::Equal => {}
RetainedEndpointEquality::NotEqual => {
return Err(CurveError::Topology(
"retained Bezier boundary loop fragments must be endpoint-connected and closed"
.into(),
));
}
RetainedEndpointEquality::Uncertified => {
return Err(CurveError::Topology(
"retained Bezier boundary loop must carry certified endpoint connectivity evidence"
.into(),
));
}
}
}
Ok(())
}
fn retained_fragment_endpoint_evidence(
fragment: &BezierSplitFragment2,
start_endpoint: bool,
policy: &CurvePolicy,
) -> CurveResult<RetainedEndpointEvidence> {
match fragment {
BezierSplitFragment2::Materialized { curve, .. } => {
let (start, end) = curve.endpoints();
Ok(RetainedEndpointEvidence {
point: Some(if start_endpoint { start } else { end }),
source: None,
})
}
BezierSplitFragment2::AlgebraicEndpointImages {
start,
end,
source_curve,
start_image,
end_image,
} => {
let parameter = if start_endpoint { start } else { end };
let image = if start_endpoint {
start_image.as_ref()
} else {
end_image.as_ref()
};
let source = source_curve
.as_ref()
.map(|source_curve| (source_curve.clone(), parameter.clone()));
let point = retained_endpoint_point_evidence(parameter, image, source_curve, policy)?;
Ok(RetainedEndpointEvidence { point, source })
}
BezierSplitFragment2::Unresolved { .. } => Err(CurveError::Topology(
"retained Bezier region boundary loops must not contain unresolved carriers".into(),
)),
}
}
fn retained_endpoint_point_evidence(
parameter: &BezierParameter2,
image: Option<&crate::BezierAlgebraicEndpointImage2>,
source_curve: &Option<BezierSubcurve2>,
policy: &CurvePolicy,
) -> CurveResult<Option<Point2>> {
if let Some(image) = image
&& let Some(point) = exact_rational_point_from_image(image.point())
{
return Ok(Some(point));
}
let BezierParameter2::Exact(value) = parameter else {
return Ok(None);
};
let Some(source_curve) = source_curve else {
return Ok(None);
};
match subcurve_point_at(source_curve, value.clone(), policy) {
Classification::Decided(point) => Ok(Some(point)),
Classification::Uncertain(reason) => Err(CurveError::Topology(format!(
"could not certify retained boundary exact endpoint from source curve: {reason:?}"
))),
}
}
fn retained_endpoint_equality(
left: &RetainedEndpointEvidence,
right: &RetainedEndpointEvidence,
policy: &CurvePolicy,
) -> RetainedEndpointEquality {
if let (Some(left), Some(right)) = (&left.point, &right.point) {
return match is_zero(&left.distance_squared(right), policy) {
Some(true) => RetainedEndpointEquality::Equal,
Some(false) => RetainedEndpointEquality::NotEqual,
None => RetainedEndpointEquality::Uncertified,
};
}
if let (Some(left), Some(right)) = (&left.source, &right.source)
&& left == right
{
return RetainedEndpointEquality::Equal;
}
RetainedEndpointEquality::Uncertified
}
impl BezierRetainedRegion2 {
pub fn new(boundary_loops: Vec<BezierRetainedBoundaryLoop2>) -> CurveResult<Self> {
validate_retained_region_loops(&boundary_loops)?;
Ok(Self { boundary_loops })
}
pub fn from_retained_arrangement_traversal(
graph: &BezierArrangementGraph2,
traversal: &BezierArrangementTraversal2,
) -> Classification<Self> {
let mut loops = Vec::with_capacity(traversal.chains().len());
for chain in traversal.chains() {
if !chain.is_closed() {
return Classification::Uncertain(UncertaintyReason::Boundary);
}
let mut fragments = Vec::with_capacity(chain.len());
let mut arrangement_sources = Vec::with_capacity(chain.len());
for index in chain.fragment_indices() {
let Some(fragment) = graph.fragments().get(*index) else {
return Classification::Uncertain(UncertaintyReason::Unsupported);
};
match fragment.fragment() {
BezierSplitFragment2::Materialized { .. }
| BezierSplitFragment2::AlgebraicEndpointImages { .. } => {
fragments.push(fragment.fragment().clone());
}
BezierSplitFragment2::Unresolved { .. } => {
return Classification::Uncertain(UncertaintyReason::Boundary);
}
}
arrangement_sources.push(BezierRetainedFragmentSource2::new(
*index,
fragment.source_curve_index(),
fragment.source_fragment_index(),
));
}
let loop_ = match BezierRetainedBoundaryLoop2::try_new_with_arrangement_sources(
fragments,
arrangement_sources,
) {
Ok(loop_) => loop_,
Err(_) => return Classification::Uncertain(UncertaintyReason::Unsupported),
};
loops.push(loop_);
}
match Self::new(loops) {
Ok(region) => Classification::Decided(region),
Err(_) => Classification::Uncertain(UncertaintyReason::Unsupported),
}
}
pub fn from_retained_linear_overlap_traversal(
traversal: &BezierRetainedLinearOverlapTraversal2,
) -> Classification<Self> {
Self::from_retained_arrangement_traversal(
traversal.refinement().graph(),
traversal.traversal(),
)
}
pub fn line_image_role_report(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierRetainedLineRegionRoleReport2>> {
let mut contours = Vec::with_capacity(self.boundary_loops.len());
let mut materialized_fragment_count = 0_usize;
let mut algebraic_fragment_count = 0_usize;
for boundary_loop in &self.boundary_loops {
let line_loop = match retained_line_loop_to_contour(boundary_loop, policy)? {
Classification::Decided(line_loop) => line_loop,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
materialized_fragment_count += line_loop.materialized_fragment_count;
algebraic_fragment_count += line_loop.algebraic_fragment_count;
contours.push(line_loop.contour);
}
let roles = match retained_line_loop_roles(&contours, policy)? {
Classification::Decided(roles) => roles,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let report = BezierRetainedLineRegionRoleReport2::new(
roles.roles,
roles.nesting_depths,
materialized_fragment_count,
algebraic_fragment_count,
contours,
)?
.with_loop_arrangement_sources(retained_loop_arrangement_sources(&self.boundary_loops))?;
Ok(Classification::Decided(report))
}
pub fn signed_area_role_report(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierRetainedSignedAreaRoleReport2>> {
let mut roles = Vec::with_capacity(self.boundary_loops.len());
let mut signed_areas = Vec::with_capacity(self.boundary_loops.len());
for boundary_loop in &self.boundary_loops {
let Some(area) = boundary_loop.signed_area()? else {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
};
let role = match real_sign(&area, policy) {
Some(RealSign::Negative) => BezierRetainedRegionLoopRole::Material,
Some(RealSign::Positive) => BezierRetainedRegionLoopRole::Hole,
Some(RealSign::Zero) => {
return Ok(Classification::Uncertain(UncertaintyReason::Boundary));
}
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
};
roles.push(role);
signed_areas.push(area);
}
let report = BezierRetainedSignedAreaRoleReport2::new(roles, signed_areas)?
.with_loop_fragment_counts(retained_loop_fragment_counts(&self.boundary_loops))?
.with_loop_arrangement_sources(retained_loop_arrangement_sources(
&self.boundary_loops,
))?;
Ok(Classification::Decided(report))
}
pub fn curved_nesting_role_report(
&self,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierRetainedCurvedNestingRoleReport2>> {
let mut native_loops = Vec::with_capacity(self.boundary_loops.len());
let mut sample_points = Vec::with_capacity(self.boundary_loops.len());
let mut signed_areas = Vec::with_capacity(self.boundary_loops.len());
for boundary_loop in &self.boundary_loops {
let native_loop = match retained_loop_to_native(boundary_loop) {
Some(loop_) => loop_,
None => return Ok(Classification::Uncertain(UncertaintyReason::Unsupported)),
};
let Some(area) = native_loop.signed_area()? else {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
};
match real_sign(&area, policy) {
Some(RealSign::Positive | RealSign::Negative) => {}
Some(RealSign::Zero) => {
return Ok(Classification::Uncertain(UncertaintyReason::Boundary));
}
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
let sample = match native_loop_sample_point(&native_loop, policy) {
Classification::Decided(point) => point,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
sample_points.push(sample);
signed_areas.push(area);
native_loops.push(native_loop);
}
let mut roles = Vec::with_capacity(native_loops.len());
let mut nesting_depths = Vec::with_capacity(native_loops.len());
for (candidate_index, sample) in sample_points.iter().enumerate() {
let mut depth = 0_usize;
for (container_index, container) in native_loops.iter().enumerate() {
if candidate_index == container_index {
continue;
}
match classify_point_against_native_loop(container, sample, policy)? {
Classification::Decided(ContourPointLocation::Inside) => depth += 1,
Classification::Decided(ContourPointLocation::Outside) => {}
Classification::Decided(ContourPointLocation::Boundary) => {
return Ok(Classification::Uncertain(UncertaintyReason::Boundary));
}
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
}
}
nesting_depths.push(depth);
roles.push(if depth.is_multiple_of(2) {
BezierRetainedRegionLoopRole::Material
} else {
BezierRetainedRegionLoopRole::Hole
});
}
let report = BezierRetainedCurvedNestingRoleReport2::new(
roles,
nesting_depths,
signed_areas,
sample_points,
)?
.with_loop_fragment_counts(retained_loop_fragment_counts(&self.boundary_loops))?
.with_loop_arrangement_sources(retained_loop_arrangement_sources(&self.boundary_loops))?;
Ok(Classification::Decided(report))
}
pub fn boundary_loops(&self) -> &[BezierRetainedBoundaryLoop2] {
&self.boundary_loops
}
pub fn into_boundary_loops(self) -> Vec<BezierRetainedBoundaryLoop2> {
self.boundary_loops
}
pub fn is_empty(&self) -> bool {
self.boundary_loops.is_empty()
}
pub fn len(&self) -> usize {
self.boundary_loops.len()
}
pub fn has_algebraic_fragments(&self) -> bool {
self.boundary_loops
.iter()
.any(BezierRetainedBoundaryLoop2::has_algebraic_fragments)
}
pub fn signed_area(&self) -> CurveResult<Option<Real>> {
let mut total = Real::zero();
for boundary_loop in &self.boundary_loops {
let Some(area) = boundary_loop.signed_area()? else {
return Ok(None);
};
total = &total + &area;
}
Ok(Some(total))
}
}
struct RetainedLineLoopContour {
contour: Contour2,
materialized_fragment_count: usize,
algebraic_fragment_count: usize,
}
fn retained_loop_arrangement_sources(
boundary_loops: &[BezierRetainedBoundaryLoop2],
) -> Vec<Option<Vec<BezierRetainedFragmentSource2>>> {
boundary_loops
.iter()
.map(|boundary_loop| boundary_loop.arrangement_sources().map(<[_]>::to_vec))
.collect()
}
fn retained_loop_fragment_counts(boundary_loops: &[BezierRetainedBoundaryLoop2]) -> Vec<usize> {
boundary_loops
.iter()
.map(BezierRetainedBoundaryLoop2::len)
.collect()
}
fn validate_loop_fragment_counts(
loop_count: usize,
loop_fragment_counts: &[usize],
) -> CurveResult<()> {
validate_report_length(
loop_count,
"loop fragment count",
loop_fragment_counts.len(),
)?;
if loop_fragment_counts.contains(&0) {
return Err(CurveError::Topology(
"retained role report loop fragment counts must be nonzero".into(),
));
}
Ok(())
}
fn validate_loop_arrangement_sources(
loop_count: usize,
loop_arrangement_sources: &[Option<Vec<BezierRetainedFragmentSource2>>],
) -> CurveResult<()> {
validate_report_length(
loop_count,
"loop arrangement source",
loop_arrangement_sources.len(),
)?;
if loop_arrangement_sources.iter().flatten().any(Vec::is_empty) {
return Err(CurveError::Topology(
"retained role report present loop arrangement sources must be nonempty".into(),
));
}
let indices = loop_arrangement_sources
.iter()
.filter_map(Option::as_ref)
.flat_map(|sources| {
sources
.iter()
.map(|source| source.arrangement_fragment_index())
})
.collect::<Vec<_>>();
validate_unique_arrangement_source_indices(
indices,
"retained role report loop arrangement sources must not reuse arrangement fragments",
)
}
fn validate_counted_loop_arrangement_source_counts(
loop_fragment_counts: Option<&[usize]>,
loop_arrangement_sources: &[Option<Vec<BezierRetainedFragmentSource2>>],
) -> CurveResult<()> {
let Some(loop_fragment_counts) = loop_fragment_counts else {
if loop_arrangement_sources.iter().any(Option::is_some) {
return Err(CurveError::Topology(
"retained role report present loop arrangement sources require loop fragment count evidence"
.into(),
));
}
return Ok(());
};
for (fragment_count, sources) in loop_fragment_counts.iter().zip(loop_arrangement_sources) {
if let Some(sources) = sources
&& sources.len() != *fragment_count
{
return Err(CurveError::Topology(
"retained role report loop source count does not match loop fragment count".into(),
));
}
}
Ok(())
}
fn validate_unique_arrangement_source_indices(
mut indices: Vec<usize>,
error: &str,
) -> CurveResult<()> {
indices.sort_unstable();
if indices.windows(2).any(|window| window[0] == window[1]) {
return Err(CurveError::Topology(error.into()));
}
Ok(())
}
fn validate_report_length(
loop_count: usize,
evidence_name: &str,
evidence_count: usize,
) -> CurveResult<()> {
if loop_count == 0 {
return Err(CurveError::Topology(
"retained role report must carry at least one loop".into(),
));
}
if loop_count != evidence_count {
return Err(CurveError::Topology(format!(
"retained role report {evidence_name} count does not match loop count"
)));
}
Ok(())
}
fn validate_nesting_depth_roles(
roles: &[BezierRetainedRegionLoopRole],
nesting_depths: &[usize],
) -> CurveResult<()> {
for (role, depth) in roles.iter().zip(nesting_depths) {
let expected = if depth.is_multiple_of(2) {
BezierRetainedRegionLoopRole::Material
} else {
BezierRetainedRegionLoopRole::Hole
};
if *role != expected {
return Err(CurveError::Topology(
"retained nesting role report role does not match certified nesting depth".into(),
));
}
}
Ok(())
}
fn validate_signed_area_roles(
roles: &[BezierRetainedRegionLoopRole],
signed_areas: &[Real],
) -> CurveResult<()> {
let policy = CurvePolicy::certified();
for (role, signed_area) in roles.iter().zip(signed_areas) {
let expected = match real_sign(signed_area, &policy) {
Some(RealSign::Negative) => BezierRetainedRegionLoopRole::Material,
Some(RealSign::Positive) => BezierRetainedRegionLoopRole::Hole,
Some(RealSign::Zero) | None => {
return Err(CurveError::Topology(
"retained signed-area role report must carry certified nonzero area evidence"
.into(),
));
}
};
if *role != expected {
return Err(CurveError::Topology(
"retained signed-area role report role does not match signed-area evidence".into(),
));
}
}
Ok(())
}
fn validate_nonzero_signed_area_evidence(signed_areas: &[Real]) -> CurveResult<()> {
let policy = CurvePolicy::certified();
for signed_area in signed_areas {
match real_sign(signed_area, &policy) {
Some(RealSign::Positive | RealSign::Negative) => {}
Some(RealSign::Zero) | None => {
return Err(CurveError::Topology(
"retained curved nesting role report must carry certified nonzero signed-area evidence"
.into(),
));
}
}
}
Ok(())
}
fn validate_line_role_report_fragment_counts(
materialized_fragment_count: usize,
algebraic_fragment_count: usize,
contours: &[Contour2],
) -> CurveResult<()> {
let source_fragment_count = materialized_fragment_count
.checked_add(algebraic_fragment_count)
.ok_or_else(|| {
CurveError::Topology(
"retained line role report source fragment count overflowed".into(),
)
})?;
let contour_fragment_count = contours
.iter()
.try_fold(0_usize, |count, contour| count.checked_add(contour.len()))
.ok_or_else(|| {
CurveError::Topology(
"retained line role report contour fragment count overflowed".into(),
)
})?;
if source_fragment_count != contour_fragment_count {
return Err(CurveError::Topology(
"retained line role report source fragment count does not match line contour evidence"
.into(),
));
}
Ok(())
}
fn validate_line_loop_arrangement_source_counts(
contours: &[Contour2],
loop_arrangement_sources: &[Option<Vec<BezierRetainedFragmentSource2>>],
) -> CurveResult<()> {
for (contour, sources) in contours.iter().zip(loop_arrangement_sources) {
if let Some(sources) = sources
&& sources.len() != contour.len()
{
return Err(CurveError::Topology(
"retained line role report loop source count does not match contour fragment count"
.into(),
));
}
}
Ok(())
}
fn retained_line_loop_to_contour(
boundary_loop: &BezierRetainedBoundaryLoop2,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedLineLoopContour>> {
let mut segments = Vec::with_capacity(boundary_loop.fragments().len());
let mut materialized_fragment_count = 0_usize;
let mut algebraic_fragment_count = 0_usize;
for fragment in boundary_loop.fragments() {
let endpoints = match retained_line_fragment_endpoints(fragment, policy)? {
Classification::Decided(endpoints) => endpoints,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
match endpoints.source {
RetainedLineFragmentSource::MaterializedFit => materialized_fragment_count += 1,
RetainedLineFragmentSource::AlgebraicEndpoints => algebraic_fragment_count += 1,
}
let (start, end) = endpoints.points;
segments.push(Segment2::Line(LineSeg2::try_new(start, end)?));
}
Contour2::try_new(segments).map(|contour| {
Classification::Decided(RetainedLineLoopContour {
contour,
materialized_fragment_count,
algebraic_fragment_count,
})
})
}
struct RetainedLineFragmentEndpoints {
points: (Point2, Point2),
source: RetainedLineFragmentSource,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RetainedLineFragmentSource {
MaterializedFit,
AlgebraicEndpoints,
}
fn retained_line_fragment_endpoints(
fragment: &BezierSplitFragment2,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedLineFragmentEndpoints>> {
match fragment {
BezierSplitFragment2::Materialized { curve, .. } => {
let fit = match subcurve_fit_exact_line_image(curve, policy)? {
Classification::Decided(BezierLineImageFitRelation::Fit(fit)) => fit,
Classification::Decided(BezierLineImageFitRelation::NotLine) => {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
Ok(Classification::Decided(RetainedLineFragmentEndpoints {
points: (fit.line().start().clone(), fit.line().end().clone()),
source: RetainedLineFragmentSource::MaterializedFit,
}))
}
BezierSplitFragment2::AlgebraicEndpointImages {
start,
end,
source_curve,
start_image,
end_image,
} => {
let start = match retained_line_endpoint_point(
start,
start_image.as_ref(),
source_curve,
policy,
) {
Classification::Decided(point) => point,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
let end =
match retained_line_endpoint_point(end, end_image.as_ref(), source_curve, policy) {
Classification::Decided(point) => point,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
Ok(Classification::Decided(RetainedLineFragmentEndpoints {
points: (start, end),
source: RetainedLineFragmentSource::AlgebraicEndpoints,
}))
}
BezierSplitFragment2::Unresolved { .. } => {
Ok(Classification::Uncertain(UncertaintyReason::Boundary))
}
}
}
fn subcurve_fit_exact_line_image(
curve: &BezierSubcurve2,
policy: &CurvePolicy,
) -> CurveResult<Classification<BezierLineImageFitRelation>> {
match curve {
BezierSubcurve2::Quadratic(curve) => curve.fit_exact_line_image(policy),
BezierSubcurve2::Cubic(curve) => curve.fit_exact_line_image(policy),
BezierSubcurve2::RationalQuadratic(curve) => curve.fit_exact_line_image(policy),
}
}
fn retained_line_endpoint_point(
parameter: &BezierParameter2,
image: Option<&crate::BezierAlgebraicEndpointImage2>,
source_curve: &Option<BezierSubcurve2>,
policy: &CurvePolicy,
) -> Classification<Point2> {
match parameter {
BezierParameter2::Exact(value) => {
let Some(source_curve) = source_curve else {
return Classification::Uncertain(UncertaintyReason::Unsupported);
};
subcurve_point_at(source_curve, value.clone(), policy)
}
BezierParameter2::Algebraic(_) => {
let Some(image) = image else {
return Classification::Uncertain(UncertaintyReason::Boundary);
};
match exact_rational_point_from_image(image.point()) {
Some(point) => Classification::Decided(point),
None => Classification::Uncertain(UncertaintyReason::Unsupported),
}
}
}
}
fn exact_rational_point_from_image(point: &BezierEndpointPointImage2) -> Option<Point2> {
match point {
BezierEndpointPointImage2::Polynomial(point) => Some(Point2::new(
point
.x()?
.representation()?
.exact_rational_witness()?
.clone(),
point
.y()?
.representation()?
.exact_rational_witness()?
.clone(),
)),
BezierEndpointPointImage2::RationalQuadratic(point) => Some(Point2::new(
point
.x()?
.representation()?
.exact_rational_witness()?
.clone(),
point
.y()?
.representation()?
.exact_rational_witness()?
.clone(),
)),
}
}
struct RetainedLoopRoleDecision {
roles: Vec<BezierRetainedRegionLoopRole>,
nesting_depths: Vec<usize>,
}
fn retained_line_loop_roles(
contours: &[Contour2],
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedLoopRoleDecision>> {
let mut roles = Vec::with_capacity(contours.len());
let mut nesting_depths = Vec::with_capacity(contours.len());
for (candidate_index, candidate) in contours.iter().enumerate() {
let sample = candidate
.segments()
.first()
.ok_or(crate::CurveError::EmptyCurveString)?
.start();
let mut depth = 0_usize;
for (container_index, container) in contours.iter().enumerate() {
if candidate_index == container_index {
continue;
}
match container.classify_point(sample, policy) {
Classification::Decided(ContourPointLocation::Inside) => depth += 1,
Classification::Decided(ContourPointLocation::Outside) => {}
Classification::Decided(ContourPointLocation::Boundary) => {
return Ok(Classification::Uncertain(UncertaintyReason::Boundary));
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
nesting_depths.push(depth);
roles.push(if depth.is_multiple_of(2) {
BezierRetainedRegionLoopRole::Material
} else {
BezierRetainedRegionLoopRole::Hole
});
}
Ok(Classification::Decided(RetainedLoopRoleDecision {
roles,
nesting_depths,
}))
}
fn retained_loop_to_native(
boundary_loop: &BezierRetainedBoundaryLoop2,
) -> Option<BezierBoundaryLoop2> {
let mut fragments = Vec::with_capacity(boundary_loop.fragments().len());
for fragment in boundary_loop.fragments() {
let BezierSplitFragment2::Materialized { curve, .. } = fragment else {
return None;
};
fragments.push(curve.clone());
}
BezierBoundaryLoop2::new(fragments).ok()
}
fn native_loop_sample_point(
boundary_loop: &BezierBoundaryLoop2,
policy: &CurvePolicy,
) -> Classification<Point2> {
let Some(fragment) = boundary_loop.fragments().first() else {
return Classification::Uncertain(UncertaintyReason::Unsupported);
};
let half = match Real::one() / Real::from(2_i8) {
Ok(half) => half,
Err(_) => return Classification::Uncertain(UncertaintyReason::Unsupported),
};
subcurve_point_at(fragment, half, policy)
}
fn classify_point_against_native_loop(
boundary_loop: &BezierBoundaryLoop2,
point: &Point2,
policy: &CurvePolicy,
) -> CurveResult<Classification<ContourPointLocation>> {
let ray = match horizontal_ray_past_loop(boundary_loop, point, policy) {
Classification::Decided(ray) => ray,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let mut crossings = 0_usize;
for fragment in boundary_loop.fragments() {
match subcurve_contains_point(fragment, point, policy) {
Classification::Decided(true) => {
return Ok(Classification::Decided(ContourPointLocation::Boundary));
}
Classification::Decided(false) => {}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
let relation = match subcurve_relation_to_line_with_contacts(fragment, &ray, policy) {
Classification::Decided(relation) => relation,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
match relation {
BezierLineContactRelation::ControlHullDisjoint { .. } => {}
BezierLineContactRelation::OnSupportingLine => {
return Ok(Classification::Uncertain(UncertaintyReason::Boundary));
}
BezierLineContactRelation::Contacts { contacts } => {
for contact in contacts {
if contact.kind() != BezierLineContactKind::Crossing {
continue;
}
let parameter_cmp = compare_reals(contact.parameter(), &Real::one(), policy);
if matches!(parameter_cmp, Some(std::cmp::Ordering::Equal)) {
continue;
}
if parameter_cmp.is_none() {
return Ok(Classification::Uncertain(UncertaintyReason::RealSign));
}
let contact_point =
match subcurve_point_at(fragment, contact.parameter().clone(), policy) {
Classification::Decided(point) => point,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
match compare_reals(contact_point.x(), point.x(), policy) {
Some(std::cmp::Ordering::Greater) => crossings += 1,
Some(std::cmp::Ordering::Equal) => {
return Ok(Classification::Decided(ContourPointLocation::Boundary));
}
Some(std::cmp::Ordering::Less) => {}
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
}
BezierLineContactRelation::IsolatedIntersections { .. }
| BezierLineContactRelation::Unresolved => {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
}
}
Ok(Classification::Decided(if crossings.is_multiple_of(2) {
ContourPointLocation::Outside
} else {
ContourPointLocation::Inside
}))
}
fn horizontal_ray_past_loop(
boundary_loop: &BezierBoundaryLoop2,
point: &Point2,
policy: &CurvePolicy,
) -> Classification<LineSeg2> {
let mut right_x = point.x() + Real::one();
for fragment in boundary_loop.fragments() {
let bounds = match subcurve_bounds(fragment, policy) {
Classification::Decided(bounds) => bounds,
Classification::Uncertain(reason) => return Classification::Uncertain(reason),
};
if matches!(
compare_reals(bounds.max_x(), &right_x, policy),
Some(std::cmp::Ordering::Greater)
) {
right_x = bounds.max_x() + Real::one();
} else if compare_reals(bounds.max_x(), &right_x, policy).is_none() {
return Classification::Uncertain(UncertaintyReason::Ordering);
}
}
match LineSeg2::try_new(point.clone(), Point2::new(right_x, point.y().clone())) {
Ok(ray) => Classification::Decided(ray),
Err(_) => Classification::Uncertain(UncertaintyReason::Unsupported),
}
}
fn subcurve_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_point_at(
curve: &BezierSubcurve2,
parameter: Real,
policy: &CurvePolicy,
) -> Classification<Point2> {
match curve {
BezierSubcurve2::Quadratic(curve) => Classification::Decided(curve.point_at(parameter)),
BezierSubcurve2::Cubic(curve) => Classification::Decided(curve.point_at(parameter)),
BezierSubcurve2::RationalQuadratic(curve) => curve.point_at(parameter, policy),
}
}
fn subcurve_contains_point(
curve: &BezierSubcurve2,
point: &Point2,
policy: &CurvePolicy,
) -> Classification<bool> {
match curve {
BezierSubcurve2::Quadratic(curve) => curve.contains_point(point, policy),
BezierSubcurve2::Cubic(_) => Classification::Decided(false),
BezierSubcurve2::RationalQuadratic(curve) => curve.contains_point(point, policy),
}
}
fn subcurve_relation_to_line_with_contacts(
curve: &BezierSubcurve2,
line: &LineSeg2,
policy: &CurvePolicy,
) -> Classification<BezierLineContactRelation> {
match curve {
BezierSubcurve2::Quadratic(curve) => curve.relation_to_line_with_contacts(line, policy),
BezierSubcurve2::Cubic(curve) => curve.relation_to_line_with_contacts(line, policy),
BezierSubcurve2::RationalQuadratic(curve) => {
curve.relation_to_line_with_contacts(line, policy)
}
}
}
impl BezierSubcurve2 {
pub fn signed_area_contribution(&self) -> CurveResult<Option<Real>> {
match self {
Self::Quadratic(curve) => curve.signed_area_contribution().map(Some),
Self::Cubic(curve) => curve.signed_area_contribution().map(Some),
Self::RationalQuadratic(curve) => curve.signed_area_contribution(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::RationalQuadraticBezier2;
fn p(x: i32, y: i32) -> Point2 {
Point2::new(Real::from(x), Real::from(y))
}
#[test]
fn retained_subcurve_point_query_preserves_projective_denominator_uncertainty() {
let conic = RationalQuadraticBezier2::try_new(
p(0, 0),
p(1, 0),
p(2, 0),
1.into(),
(-1).into(),
1.into(),
)
.unwrap();
let subcurve = BezierSubcurve2::RationalQuadratic(conic);
assert_eq!(
subcurve_contains_point(&subcurve, &p(100, 0), &CurvePolicy::certified()),
Classification::Uncertain(UncertaintyReason::Boundary)
);
}
}