Skip to main content

axiolid_profile/
center_line.rs

1//! Centre-line profiles: an open path plus a constant width.
2
3use axiolid_core::Scalar;
4
5use crate::contour::Contour;
6
7/// An open path swept by a constant width, centred on the path.
8///
9/// This is a *representation*, not a contour: the closed area it denotes is
10/// the set of points within `half_width` of the path. Resolving it into a
11/// boundary is an offsetting algorithm, which belongs above this tier.
12///
13/// Keeping it exact matters because offsetting is lossy in a way that cannot
14/// be undone. Once a centre line is flattened into an outline, the fact that
15/// it *was* a constant-width path is gone, and with it the ability to change
16/// the width, re-offset at a different tolerance, or round-trip the source
17/// unchanged. Sheet metal, curtain-wall mullions and pipe centre lines are all
18/// authored this way.
19///
20/// The path is held as a [`Contour`] because a centre line is a sequence of
21/// bounded curve segments exactly like a boundary is; the difference is that
22/// this one is not closed and does not enclose anything on its own.
23#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(
25    feature = "serde",
26    derive(serde::Serialize, serde::Deserialize),
27    serde(deny_unknown_fields)
28)]
29pub struct CenterLineProfile {
30    /// Open path the width is measured from.
31    pub path: Contour,
32    /// Half the constant width, measured perpendicular to the path.
33    ///
34    /// Stored as a half-width rather than a full thickness so the two offset
35    /// sides are symmetric by construction: a consumer offsets by `+half` and
36    /// `-half` without having to remember to halve anything. Halving at the
37    /// boundary is the kind of factor-of-two error that produces geometry
38    /// which looks plausible and is wrong.
39    pub half_width: Scalar,
40}
41
42impl CenterLineProfile {
43    /// Construct from an open path and the full width across it.
44    pub fn from_width(path: Contour, width: Scalar) -> Self {
45        Self {
46            path,
47            half_width: width / 2.0,
48        }
49    }
50
51    /// The full width across the path.
52    pub fn width(&self) -> Scalar {
53        self.half_width * 2.0
54    }
55}