axiolid_model/curve_relation.rs
1//! Curve relationships that require graph references.
2
3use axiolid_core::{Point2, Point3, Scalar, Vec3};
4
5use crate::{NodeId, RunStation, Station, StationFrame};
6
7/// One trim selector preserved from a source representation.
8#[non_exhaustive]
9#[derive(Debug, Clone, Copy, PartialEq)]
10#[cfg_attr(
11 feature = "serde",
12 derive(serde::Serialize, serde::Deserialize),
13 serde(deny_unknown_fields)
14)]
15pub enum TrimSelector {
16 /// Curve parameter.
17 Parameter(Scalar),
18 /// Two-dimensional point.
19 Point2(Point2),
20 /// Three-dimensional point.
21 Point3(Point3),
22 /// Arc length along the basis curve.
23 ///
24 /// Selects the basis parameter at which the arc length along the basis,
25 /// measured from the basis curve's parameter `0` in the sense of the
26 /// trim, equals the value: in increasing parameter when the trim agrees
27 /// with the basis, in decreasing parameter when it does not. A negative
28 /// value measures the other way.
29 ///
30 /// The value is exact stored data, and it is a parameter-kind selector:
31 /// it names a position by a measure along the curve, not by a point, so
32 /// it satisfies [`TrimmingPreference::Parameter`]. Most curves have no
33 /// closed-form arc length (a cubic parabola's is elliptic), so an
34 /// evaluator resolves the parameter by quadrature and a root find to a
35 /// tolerance it states, and a path that cannot refuses by name.
36 ArcLength(Scalar),
37}
38
39/// Preference when both parameter and Cartesian trim selectors exist.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[cfg_attr(
42 feature = "serde",
43 derive(serde::Serialize, serde::Deserialize),
44 serde(deny_unknown_fields)
45)]
46pub enum TrimmingPreference {
47 /// Prefer parameter values.
48 Parameter,
49 /// Prefer Cartesian points.
50 Cartesian,
51 /// Use source order when no preference was stated.
52 Unspecified,
53}
54
55/// Continuity declared between consecutive composite segments.
56#[non_exhaustive]
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58#[cfg_attr(
59 feature = "serde",
60 derive(serde::Serialize, serde::Deserialize),
61 serde(deny_unknown_fields)
62)]
63pub enum Transition {
64 /// Discontinuous.
65 Discontinuous,
66 /// Position continuous.
67 Continuous,
68 /// Position and tangent continuous.
69 ContinuousSameGradient,
70 /// Position, tangent, and curvature continuous.
71 ContinuousSameGradientSameCurvature,
72}
73
74/// One oriented curve in a composite.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76#[cfg_attr(
77 feature = "serde",
78 derive(serde::Serialize, serde::Deserialize),
79 serde(deny_unknown_fields)
80)]
81pub struct CurveSegment {
82 /// Child curve.
83 pub curve: NodeId,
84 /// Whether child parameterization agrees with composite orientation.
85 pub same_sense: bool,
86 /// Transition from the preceding segment.
87 pub transition: Transition,
88}
89
90/// Relationship between curve nodes.
91#[non_exhaustive]
92#[derive(Debug, Clone, PartialEq)]
93#[cfg_attr(
94 feature = "serde",
95 derive(serde::Serialize, serde::Deserialize),
96 serde(deny_unknown_fields)
97)]
98pub enum CurveRelation {
99 /// Ordered composite curve.
100 Composite { segments: Vec<CurveSegment> },
101 /// Trimmed view of a basis curve.
102 Trimmed {
103 basis: NodeId,
104 start: Vec<TrimSelector>,
105 end: Vec<TrimSelector>,
106 sense_agreement: bool,
107 preference: TrimmingPreference,
108 },
109 /// Constant-distance offset.
110 Offset {
111 basis: NodeId,
112 distance: Scalar,
113 reference_direction: Option<Vec3>,
114 },
115 /// Three-dimensional curve associated with one or more surfaces/pcurves.
116 SurfaceCurve {
117 curve_3d: NodeId,
118 /// The parametric sides, each pairing a surface with its own p-curve.
119 sides: SurfaceSides,
120 master: MasterRepresentation,
121 },
122 /// Two-dimensional parameter curve on a surface.
123 ParameterCurve {
124 basis_surface: NodeId,
125 reference_curve: NodeId,
126 },
127 /// A curve through offsets given at stations along a basis curve
128 /// (#241).
129 ///
130 /// The curve runs from the first station's distance to the last's; at
131 /// a distance `s` between two stations it is the point the offsets
132 /// linearly interpolated in `s` give in the basis curve's `frame` at
133 /// `s` (see [`crate::station`]). It is a 3D curve whatever the basis:
134 /// a 2D basis lies in `z = 0`. At least two stations, strictly
135 /// increasing in distance, are required.
136 OffsetByStations {
137 basis: NodeId,
138 stations: Vec<Station>,
139 frame: StationFrame,
140 },
141 /// A curve through offsets given at stations along a basis curve, each
142 /// station with the side of a seam it reads and a seam-snapping window
143 /// (#301): the general form of [`CurveRelation::OffsetByStations`].
144 ///
145 /// The curve is the one `OffsetByStations` names for the stations at
146 /// their snapped distances: a windowed station within its window of
147 /// one seam of the basis stands on that seam, and the side is read
148 /// where the run does not decide it (see [`crate::station`]). At least
149 /// two stations, strictly increasing in distance, are required.
150 OffsetsAtStations {
151 basis: NodeId,
152 stations: Vec<RunStation>,
153 frame: StationFrame,
154 },
155}
156
157/// Which representation governs a redundant surface-curve definition.
158#[non_exhaustive]
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
160#[cfg_attr(
161 feature = "serde",
162 derive(serde::Serialize, serde::Deserialize),
163 serde(deny_unknown_fields)
164)]
165pub enum MasterRepresentation {
166 /// Three-dimensional curve.
167 Curve3d,
168 /// The p-curve on the FIRST parametric side governs.
169 ParameterCurveS1,
170 /// The p-curve on the SECOND parametric side governs.
171 ///
172 /// Only meaningful on a two-sided curve; naming it on a single-sided one
173 /// is contradictory rather than merely unusual, and is refused.
174 ParameterCurveS2,
175 /// Both are authoritative and must agree.
176 Both,
177 /// Unspecified.
178 Unspecified,
179}
180
181/// Which parametric side of a surface curve a p-curve belongs to.
182///
183/// A surface curve is the intersection of two surfaces, so each side owns one
184/// surface and the p-curve that is that curve's image in the surface's own
185/// parameter domain. Pairing them here is what stops a consumer from having to
186/// guess which p-curve to trim with -- a guess that would otherwise require
187/// re-inverting the surface, the exact operation the p-curve exists to avoid.
188///
189/// A single side is legitimate: not every edge has two parametric images.
190#[derive(Debug, Clone, PartialEq, Eq, Hash)]
191#[cfg_attr(
192 feature = "serde",
193 derive(serde::Serialize, serde::Deserialize),
194 serde(deny_unknown_fields)
195)]
196pub struct SurfaceSides {
197 first: (NodeId, NodeId),
198 second: Option<(NodeId, NodeId)>,
199}
200
201impl SurfaceSides {
202 /// One parametric side: a surface and this curve's image in it.
203 #[must_use]
204 pub const fn one(surface: NodeId, pcurve: NodeId) -> Self {
205 Self {
206 first: (surface, pcurve),
207 second: None,
208 }
209 }
210
211 /// Both parametric sides, in the order the authoring format states them.
212 ///
213 /// The order is load-bearing: it is what `ParameterCurveS1` and
214 /// `ParameterCurveS2` name.
215 #[must_use]
216 pub const fn two(
217 first_surface: NodeId,
218 first_pcurve: NodeId,
219 second_surface: NodeId,
220 second_pcurve: NodeId,
221 ) -> Self {
222 Self {
223 first: (first_surface, first_pcurve),
224 second: Some((second_surface, second_pcurve)),
225 }
226 }
227
228 /// The first side as `(surface, pcurve)`.
229 #[must_use]
230 pub const fn first(&self) -> (NodeId, NodeId) {
231 self.first
232 }
233
234 /// The second side as `(surface, pcurve)`, if this curve has one.
235 #[must_use]
236 pub const fn second(&self) -> Option<(NodeId, NodeId)> {
237 self.second
238 }
239
240 /// Whether both parametric sides are present.
241 #[must_use]
242 pub const fn is_two_sided(&self) -> bool {
243 self.second.is_some()
244 }
245
246 /// Every node this pairing references, surfaces and p-curves alike.
247 #[must_use]
248 pub fn references(&self) -> Vec<NodeId> {
249 let mut out = vec![self.first.0, self.first.1];
250 if let Some((surface, pcurve)) = self.second {
251 out.extend([surface, pcurve]);
252 }
253 out
254 }
255}
256
257impl CurveRelation {
258 pub(crate) fn references(&self, out: &mut Vec<NodeId>) {
259 match self {
260 Self::Composite { segments } => out.extend(segments.iter().map(|item| item.curve)),
261 Self::Trimmed { basis, .. }
262 | Self::Offset { basis, .. }
263 | Self::OffsetByStations { basis, .. }
264 | Self::OffsetsAtStations { basis, .. } => out.push(*basis),
265 Self::SurfaceCurve {
266 curve_3d, sides, ..
267 } => {
268 out.push(*curve_3d);
269 out.extend(sides.references());
270 }
271 Self::ParameterCurve {
272 basis_surface,
273 reference_curve,
274 } => out.extend([*basis_surface, *reference_curve]),
275 }
276 }
277}