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}