Skip to main content

axiolid_mesh_section_contract/
cut.rs

1//! Opt-in cut regions: the closed regions where the solid's material lies on
2//! both sides of the cutting plane (ADR 0092, #372).
3//!
4//! A request made with
5//! [`SectionRequest::with_cut_regions`](crate::SectionRequest::with_cut_regions)
6//! also reports the cut as [`CutRegion`]s. The cut is the closure of the plane
7//! points that have the solid's material on both sides: its interior meets the
8//! plane there. In-plane faces of one side are not part of it; they stay
9//! [`InPlaneRegion`](crate::InPlaneRegion)s with their side. Where faces of
10//! both sides coincide the material is on both sides, so every
11//! [`Both`](crate::InPlaneSide::Both) region is a cut region, equal to it bit
12//! for bit. A region edge bounds the cut where the off-plane triangle next to
13//! it leaves the plane on the side opposite the region's material.
14//!
15//! A consumer therefore needs no topology of its own: a cut just below the
16//! plane is the cut regions together with the `Below` regions, and a cut just
17//! above the plane is the cut regions together with the `Above` regions. Their
18//! rings run through bit-identical vertices.
19//!
20//! # Invariants
21//!
22//! - Only a [`SectionRole::Solid`](crate::SectionRole::Solid) has a cut; a
23//!   sheet request with cut regions is invalid input.
24//! - An outer ring is counter-clockwise and a hole is clockwise, so the
25//!   material lies on the left; this holds on the exact points (see
26//!   Exactness). For a ring within rounding of degenerate, the float area of
27//!   the returned points may not have the declared sign: use the declared
28//!   role (outer or hole) and never recompute it from the area. Every ring
29//!   has at least three points, no adjacent duplicates (wrap-around
30//!   included), and starts at its lexicographically smallest point (`x`, then
31//!   `y`, by `total_cmp`). It has no repeated vertex on the exact points; a
32//!   returned ring may, rarely, repeat a vertex after rounding. Holes are
33//!   sorted by point sequence, regions by `outer`.
34//! - On the exact geometry, rings never cross and have no repeated vertex, and
35//!   regions have disjoint interiors. The returned points are those exact
36//!   points computed in binary64, so rings that touch may cross or overlap by
37//!   rounding error in the returned coordinates.
38//! - Where the cut's boundary meets itself at a vertex (a pinch), the rings
39//!   are split there: each region is one connected piece of the cut's inside,
40//!   written as simple rings that may touch each other, or other regions'
41//!   rings, at that vertex (two corners touching give two regions; a hole
42//!   touching its outer ring is a hole that touches it).
43//! - A ring keeps a collinear vertex only where something else does: a
44//!   junction of a chain and a region edge, a vertex that a region ring keeps,
45//!   or a chain vertex that chain simplification keeps (a vertex of an
46//!   in-plane triangle, or a node whose neighbours are not exactly collinear).
47//!   Any other vertex between two collinear neighbours is dropped.
48//! - Every closed chain of a solid is a cut ring, bit for bit. Every open
49//!   chain with both ends on a region is a contiguous run of exactly one cut
50//!   ring, forward or reversed. Every cut ring vertex is bit-identical to a
51//!   chain point or a region ring vertex.
52//! - Every [`Both`](crate::InPlaneSide::Both) region is a cut region: the
53//!   same outer ring and the same holes, bit for bit. Where such a region
54//!   meets transversal cut area it is a separate cut region that shares the
55//!   boundary in the opposite direction.
56//!
57//! # Exactness
58//!
59//! Which edges bound the cut and how they link come from exact source
60//! predicates. Outer or hole, nesting, contact and crossing are decided
61//! exactly on source data too: the points are source vertices in the plane and
62//! exact crossings of source edges with the plane through `origin`,
63//! `origin + x` and `origin + y`, never the returned coordinates. A self-
64//! intersecting mesh is not certified (ADR 0033); detected crossings are
65//! refused by name. The check for adjacent duplicates among the returned
66//! points is a fail-closed check of the output (the registry requires it),
67//! not a geometric decision. Shells that overlap in the input, even by the rounding of
68//! the consumer's own transform, are still refused.
69
70use axiolid_core::Point2;
71
72/// Refusal detail: the cut boundary meets itself at a vertex in a way the
73/// provider does not pair: an edge in and an edge out along one ray, or a
74/// chain through the vertex that the rings would part; a provider may also
75/// refuse every pinch.
76pub const REFUSED_PINCHED_CUT_REGION: &str = "cut region boundary pinches at a vertex";
77/// Refusal detail: rings of the cut cross or overlap each other, such as two
78/// boundary edges of one direction along one ray at a vertex.
79pub const REFUSED_CROSSING_CUT_RINGS: &str = "cut rings cross or overlap";
80
81/// Hard bounds on the cut-region work of one request.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83#[non_exhaustive]
84pub struct CutRegionLimits {
85    /// Maximum output cut regions.
86    pub max_regions: usize,
87    /// Maximum vertices across all output cut region rings.
88    pub max_region_vertices: usize,
89    /// Maximum candidate edge pairs of the crossing sweep plus ring pairs of
90    /// the containment test.
91    pub max_pairs: usize,
92}
93
94impl CutRegionLimits {
95    /// Construct explicit cut-region limits.
96    pub const fn new(max_regions: usize, max_region_vertices: usize, max_pairs: usize) -> Self {
97        Self {
98            max_regions,
99            max_region_vertices,
100            max_pairs,
101        }
102    }
103}
104
105/// A planar region of the plane where the solid has material on both sides.
106#[derive(Debug, Clone, PartialEq)]
107#[non_exhaustive]
108pub struct CutRegion {
109    /// Counter-clockwise outer ring.
110    pub outer: Vec<Point2>,
111    /// Clockwise hole rings.
112    pub holes: Vec<Vec<Point2>>,
113}
114
115impl CutRegion {
116    /// Construct a cut region.
117    pub fn new(outer: Vec<Point2>, holes: Vec<Vec<Point2>>) -> Self {
118        Self { outer, holes }
119    }
120}