Skip to main content

axiolid_curve/
torus_section.rs

1//! Where a plane or sphere cuts a torus, read in the torus's own parameters
2//! (ADR 0076).
3//!
4//! A torus is not ruled, but at a fixed tube angle `v` its points form a
5//! circle about the axis, and a plane or sphere meets that circle where
6//!
7//! ```text
8//! A(v) cos(u) + B(v) sin(u) = C(v),
9//! ```
10//!
11//! with `A`, `B`, `C` trigonometric in `v`. So the section is `u` as a
12//! function of `v`, in closed form, wherever `E = A^2 + B^2 - C^2 >= 0`:
13//!
14//! ```text
15//! cos u = (A C - s B sqrt E) / (A^2 + B^2),
16//! sin u = (B C + s A sqrt E) / (A^2 + B^2),     s = +1 or -1.
17//! ```
18//!
19//! [`AngleGraph2`] holds that as a pcurve; [`TorusSection3`] the same curve
20//! in space. The angle is returned in `(-pi, pi]`; a piece is built so it
21//! never crosses `u = pi`, where that range wraps.
22
23use axiolid_core::{Frame3, Point3, Scalar, Vec3};
24
25use crate::quadric_section::{Branch, Trig2};
26
27/// The solution `u(t)` of `a(t) cos u + b(t) sin u = c(t)` chosen by
28/// `branch`, as a graph over its second coordinate: the point at `t` is
29/// `(u(t), t)`.
30#[derive(Debug, Clone, Copy, PartialEq)]
31#[cfg_attr(
32    feature = "serde",
33    derive(serde::Serialize, serde::Deserialize),
34    serde(deny_unknown_fields)
35)]
36pub struct AngleGraph2 {
37    /// Coefficient of `cos u`.
38    pub a: Trig2,
39    /// Coefficient of `sin u`.
40    pub b: Trig2,
41    /// Right-hand side.
42    pub c: Trig2,
43    /// Which of the two solutions.
44    pub branch: Branch,
45}
46
47impl AngleGraph2 {
48    /// The angle at `t`, in `(-pi, pi]`, or `None` where no solution exists.
49    #[must_use]
50    pub fn angle(&self, t: Scalar) -> Option<Scalar> {
51        let (a, b, c) = (self.a.value(t), self.b.value(t), self.c.value(t));
52        let rr = a * a + b * b;
53        if rr == 0.0 {
54            return None;
55        }
56        let e = rr - c * c;
57        // An exact zero at a branch end rounds either way; the span says
58        // the point is on the curve.
59        let size =
60            |t: &Trig2| t.constant.abs() + t.cos.abs() + t.sin.abs() + t.cos2.abs() + t.sin2.abs();
61        let scale = size(&self.a).powi(2) + size(&self.b).powi(2) + size(&self.c).powi(2);
62        if e < -1e-12 * scale {
63            return None;
64        }
65        let root = e.max(0.0).sqrt();
66        let s = self.branch.sign();
67        let u = (b * c + s * a * root).atan2(a * c - s * b * root);
68        u.is_finite().then_some(u)
69    }
70
71    /// `du/dt`, by differentiating `a cos u + b sin u - c = 0` implicitly;
72    /// `None` at a branch end, where the tangent is parallel to `u`.
73    #[must_use]
74    pub fn slope(&self, t: Scalar) -> Option<Scalar> {
75        let u = self.angle(t)?;
76        let (s, c) = u.sin_cos();
77        let f_t = self.a.derivative(t) * c + self.b.derivative(t) * s - self.c.derivative(t);
78        let f_u = -self.a.value(t) * s + self.b.value(t) * c;
79        let slope = -f_t / f_u;
80        slope.is_finite().then_some(slope)
81    }
82
83    /// `d2u/dt2`.
84    #[must_use]
85    pub fn bend(&self, t: Scalar) -> Option<Scalar> {
86        let u = self.angle(t)?;
87        let p = self.slope(t)?;
88        let (s, c) = u.sin_cos();
89        let (a, b) = (self.a.value(t), self.b.value(t));
90        let (da, db) = (self.a.derivative(t), self.b.derivative(t));
91        let f_u = -a * s + b * c;
92        let f_tt = self.a.second(t) * c + self.b.second(t) * s - self.c.second(t);
93        let f_tu = -da * s + db * c;
94        let f_uu = -a * c - b * s;
95        let bend = -(f_tt + 2.0 * f_tu * p + f_uu * p * p) / f_u;
96        bend.is_finite().then_some(bend)
97    }
98
99    /// Whether every coefficient is finite.
100    #[must_use]
101    pub fn is_finite(&self) -> bool {
102        self.a.is_finite() && self.b.is_finite() && self.c.is_finite()
103    }
104}
105
106/// A torus, as the curve needs it, in `axiolid_surface::Torus`'s
107/// parameterisation: `O + (R + r cos v)(cos u X + sin u Y) + r sin v Z`.
108#[derive(Debug, Clone, Copy, PartialEq)]
109#[cfg_attr(
110    feature = "serde",
111    derive(serde::Serialize, serde::Deserialize),
112    serde(deny_unknown_fields)
113)]
114pub struct TorusCarrier {
115    /// Local frame; `z` is the axis.
116    pub frame: Frame3,
117    /// Distance from the axis to the tube centre.
118    pub major_radius: Scalar,
119    /// Tube radius.
120    pub minor_radius: Scalar,
121}
122
123impl TorusCarrier {
124    /// The torus point at `(u, v)`.
125    #[must_use]
126    pub fn point(&self, u: Scalar, v: Scalar) -> Point3 {
127        let (su, cu) = u.sin_cos();
128        let (sv, cv) = v.sin_cos();
129        let ring = self.major_radius + self.minor_radius * cv;
130        self.frame.origin
131            + self.frame.x * (ring * cu)
132            + self.frame.y * (ring * su)
133            + self.frame.z * (self.minor_radius * sv)
134    }
135
136    /// `(dP/du, dP/dv)`.
137    #[must_use]
138    pub fn partials(&self, u: Scalar, v: Scalar) -> (Vec3, Vec3) {
139        let (su, cu) = u.sin_cos();
140        let (sv, cv) = v.sin_cos();
141        let ring = self.major_radius + self.minor_radius * cv;
142        let r = self.minor_radius;
143        (
144            self.frame.x * (-ring * su) + self.frame.y * (ring * cu),
145            self.frame.x * (-r * sv * cu) + self.frame.y * (-r * sv * su) + self.frame.z * (r * cv),
146        )
147    }
148}
149
150/// A plane's or sphere's section of a torus, in space: the torus point at
151/// `(graph.angle(t), t)`.
152#[derive(Debug, Clone, Copy, PartialEq)]
153#[cfg_attr(
154    feature = "serde",
155    derive(serde::Serialize, serde::Deserialize),
156    serde(deny_unknown_fields)
157)]
158pub struct TorusSection3 {
159    /// The torus the curve lies on.
160    pub torus: TorusCarrier,
161    /// The curve in the torus's parameters.
162    pub graph: AngleGraph2,
163}
164
165impl TorusSection3 {
166    /// Point at `t`.
167    #[must_use]
168    pub fn point(&self, t: Scalar) -> Option<Point3> {
169        Some(self.torus.point(self.graph.angle(t)?, t))
170    }
171
172    /// Tangent at `t`: `P_u du/dt + P_v`.
173    #[must_use]
174    pub fn tangent(&self, t: Scalar) -> Option<Vec3> {
175        let u = self.graph.angle(t)?;
176        let (pu, pv) = self.torus.partials(u, t);
177        Some(pu * self.graph.slope(t)? + pv)
178    }
179
180    /// Second derivative at `t`: `P_uu u'^2 + 2 P_uv u' + P_vv + P_u u''`.
181    #[must_use]
182    pub fn bend(&self, t: Scalar) -> Option<Vec3> {
183        let u = self.graph.angle(t)?;
184        let p = self.graph.slope(t)?;
185        let q = self.graph.bend(t)?;
186        let (su, cu) = u.sin_cos();
187        let (sv, cv) = t.sin_cos();
188        let (x, y, z) = (self.torus.frame.x, self.torus.frame.y, self.torus.frame.z);
189        let r = self.torus.minor_radius;
190        let ring = self.torus.major_radius + r * cv;
191        let p_u = x * (-ring * su) + y * (ring * cu);
192        let p_uu = x * (-ring * cu) + y * (-ring * su);
193        let p_uv = x * (r * sv * su) + y * (-r * sv * cu);
194        let p_vv = x * (-r * cv * cu) + y * (-r * cv * su) + z * (-r * sv);
195        Some(p_uu * (p * p) + p_uv * (2.0 * p) + p_vv + p_u * q)
196    }
197}