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}