Skip to main content

brepkit_offset/
data.rs

1#![allow(dead_code)]
2//! Central data structures shared across all offset pipeline phases.
3
4use std::collections::{BTreeMap, HashMap};
5
6use brepkit_math::tolerance::Tolerance;
7use brepkit_math::vec::Point3;
8use brepkit_topology::Topology;
9use brepkit_topology::edge::EdgeId;
10use brepkit_topology::face::{FaceId, FaceSurface};
11use brepkit_topology::vertex::{Vertex, VertexId};
12use brepkit_topology::wire::WireId;
13
14/// Classification of an edge based on the dihedral angle between its
15/// two adjacent faces.
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum EdgeClass {
18    /// The two faces are tangent-continuous across this edge.
19    Tangent,
20    /// The edge is convex (outside corner) with the given dihedral angle in
21    /// radians.
22    Convex {
23        /// Dihedral angle in radians (0, pi).
24        angle: f64,
25    },
26    /// The edge is concave (inside corner) with the given dihedral angle in
27    /// radians.
28    Concave {
29        /// Dihedral angle in radians (0, pi).
30        angle: f64,
31    },
32}
33
34/// Classification of a vertex based on its surrounding edge classes.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum VertexClass {
37    /// All incident edges are convex or tangent.
38    Convex,
39    /// All incident edges are concave or tangent.
40    Concave,
41    /// The vertex has both convex and concave incident edges.
42    Mixed,
43}
44
45/// Tracking status for a single offset face.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum OffsetStatus {
48    /// The face was successfully offset.
49    Done,
50    /// The face was excluded from offsetting (e.g. thick-solid open faces).
51    Excluded,
52    /// The face offset failed and was skipped.
53    Failed,
54}
55
56/// An offset face: the original face, its offset surface, and status.
57#[derive(Debug, Clone)]
58pub struct OffsetFace {
59    /// The original face that was offset.
60    pub original: FaceId,
61    /// The offset surface geometry.
62    pub surface: FaceSurface,
63    /// The signed offset distance applied.
64    pub distance: f64,
65    /// Current status of this offset face.
66    pub status: OffsetStatus,
67}
68
69/// The intersection curve between two adjacent offset faces, replacing
70/// the original shared edge.
71#[derive(Debug, Clone)]
72pub struct FaceIntersection {
73    /// The original edge shared by the two faces.
74    pub original_edge: EdgeId,
75    /// First adjacent face.
76    pub face_a: FaceId,
77    /// Second adjacent face.
78    pub face_b: FaceId,
79    /// Sampled points along the intersection curve.
80    pub curve_points: Vec<Point3>,
81    /// New edges created from this intersection.
82    pub new_edges: Vec<EdgeId>,
83}
84
85/// A split point on an edge, recording the parameter value and the vertex
86/// created at that location.
87#[derive(Debug, Clone)]
88pub struct SplitPoint {
89    /// Parameter value on the original edge curve.
90    pub parameter: f64,
91    /// The vertex inserted at this split.
92    pub vertex: VertexId,
93}
94
95/// Record of how an original edge was split into sub-edges.
96#[derive(Debug, Clone)]
97pub struct EdgeSplitRecord {
98    /// The original edge before splitting.
99    pub original: EdgeId,
100    /// Ordered split points along the edge.
101    pub splits: Vec<SplitPoint>,
102    /// The new edges produced after splitting.
103    pub new_edges: Vec<EdgeId>,
104}
105
106/// Strategy for joining adjacent offset faces at convex edges.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108pub enum JointType {
109    /// Extend faces until they intersect (sharp corners).
110    #[default]
111    Intersection,
112    /// Insert a rolling-ball arc fillet between faces.
113    Arc,
114}
115
116/// Configuration options for solid offset.
117#[derive(Debug, Clone)]
118pub struct OffsetOptions {
119    /// How to join offset faces at convex edges.
120    pub joint: JointType,
121    /// Geometric tolerance for intersection and fitting.
122    pub tolerance: Tolerance,
123    /// Whether to detect and remove global self-intersections.
124    pub remove_self_intersections: bool,
125}
126
127#[allow(clippy::derivable_impls)] // Explicit: documents that SI removal defaults to off
128impl Default for OffsetOptions {
129    fn default() -> Self {
130        Self {
131            joint: JointType::default(),
132            tolerance: Tolerance::default(),
133            // Default to false until SI removal is fully implemented.
134            remove_self_intersections: false,
135        }
136    }
137}
138
139/// Accumulated data from all phases of the offset pipeline.
140///
141/// Each phase reads from earlier fields and writes its own outputs.
142#[derive(Debug, Clone)]
143pub struct OffsetData {
144    // --- Configuration ---
145    /// The signed offset distance.
146    pub distance: f64,
147    /// Pipeline options.
148    pub options: OffsetOptions,
149    /// Faces excluded from offsetting (kept as-is in thick solid).
150    pub excluded_faces: Vec<FaceId>,
151
152    // --- Phase 1: analysis ---
153    /// Edge convexity classification. Keys are edge indices from
154    /// `edge_to_face_map`.
155    pub edge_class: BTreeMap<usize, EdgeClass>,
156    /// Vertex classification derived from incident edge classes. Keys are
157    /// vertex arena indices.
158    pub vertex_class: BTreeMap<usize, VertexClass>,
159
160    // --- Phase 2: offset surfaces ---
161    /// Offset face for each original face.
162    pub offset_faces: HashMap<FaceId, OffsetFace>,
163
164    // --- Phase 3 & 4: intersections ---
165    /// Intersection curves between adjacent offset faces.
166    pub intersections: Vec<FaceIntersection>,
167
168    // --- Phase 5: edge splitting ---
169    /// Records of how original edges were split at intersection points.
170    pub edge_splits: BTreeMap<usize, EdgeSplitRecord>,
171
172    /// Boundary edges: original edges shared between an excluded face and a
173    /// non-excluded face. Keyed by the non-excluded `FaceId`, value is the
174    /// list of original `EdgeId`s on that boundary. Used by the wire builder
175    /// to include these edges in the non-excluded face's loop.
176    pub boundary_edges: HashMap<FaceId, Vec<EdgeId>>,
177
178    // --- Phase 6: arc joints ---
179    /// Faces created as rolling-ball arc joints at convex edges.
180    pub joint_faces: Vec<FaceId>,
181
182    // --- Phase 7: loops ---
183    /// Wire loops built for each offset face from trimmed intersection
184    /// curves.
185    pub face_wires: HashMap<FaceId, Vec<WireId>>,
186}
187
188impl OffsetData {
189    /// Create a new, empty `OffsetData` with the given configuration.
190    #[must_use]
191    pub fn new(distance: f64, options: OffsetOptions, excluded_faces: Vec<FaceId>) -> Self {
192        Self {
193            distance,
194            options,
195            excluded_faces,
196            edge_class: BTreeMap::new(),
197            vertex_class: BTreeMap::new(),
198            offset_faces: HashMap::new(),
199            intersections: Vec::new(),
200            edge_splits: BTreeMap::new(),
201            boundary_edges: HashMap::new(),
202            joint_faces: Vec::new(),
203            face_wires: HashMap::new(),
204        }
205    }
206}
207
208/// Find an existing vertex within `tol` of `point`, or create a new one.
209///
210/// Shared helper used by `inter2d` and `loops` to avoid duplicate vertices
211/// at the same 3D position. The `cache` accumulates all vertices created
212/// during the current phase.
213pub fn find_or_create_vertex(
214    topo: &mut Topology,
215    cache: &mut Vec<(Point3, VertexId)>,
216    point: Point3,
217    tol: f64,
218) -> VertexId {
219    for &(cached_pt, vid) in cache.iter() {
220        let dx = point.x() - cached_pt.x();
221        let dy = point.y() - cached_pt.y();
222        let dz = point.z() - cached_pt.z();
223        if dx * dx + dy * dy + dz * dz <= tol * tol {
224            return vid;
225        }
226    }
227
228    let vid = topo.add_vertex(Vertex::new(point, tol));
229    cache.push((point, vid));
230    vid
231}