Skip to main content

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}