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)]
31pub struct AngleGraph2 {
32    /// Coefficient of `cos u`.
33    pub a: Trig2,
34    /// Coefficient of `sin u`.
35    pub b: Trig2,
36    /// Right-hand side.
37    pub c: Trig2,
38    /// Which of the two solutions.
39    pub branch: Branch,
40}
41
42impl AngleGraph2 {
43    /// The angle at `t`, in `(-pi, pi]`, or `None` where no solution exists.
44    #[must_use]
45    pub fn angle(&self, t: Scalar) -> Option<Scalar> {
46        let (a, b, c) = (self.a.value(t), self.b.value(t), self.c.value(t));
47        let rr = a * a + b * b;
48        if rr == 0.0 {
49            return None;
50        }
51        let e = rr - c * c;
52        // An exact zero at a branch end rounds either way; the span says
53        // the point is on the curve.
54        let size =
55            |t: &Trig2| t.constant.abs() + t.cos.abs() + t.sin.abs() + t.cos2.abs() + t.sin2.abs();
56        let scale = size(&self.a).powi(2) + size(&self.b).powi(2) + size(&self.c).powi(2);
57        if e < -1e-12 * scale {
58            return None;
59        }
60        let root = e.max(0.0).sqrt();
61        let s = self.branch.sign();
62        let u = (b * c + s * a * root).atan2(a * c - s * b * root);
63        u.is_finite().then_some(u)
64    }
65
66    /// `du/dt`, by differentiating `a cos u + b sin u - c = 0` implicitly;
67    /// `None` at a branch end, where the tangent is parallel to `u`.
68    #[must_use]
69    pub fn slope(&self, t: Scalar) -> Option<Scalar> {
70        let u = self.angle(t)?;
71        let (s, c) = u.sin_cos();
72        let f_t = self.a.derivative(t) * c + self.b.derivative(t) * s - self.c.derivative(t);
73        let f_u = -self.a.value(t) * s + self.b.value(t) * c;
74        let slope = -f_t / f_u;
75        slope.is_finite().then_some(slope)
76    }
77
78    /// `d2u/dt2`.
79    #[must_use]
80    pub fn bend(&self, t: Scalar) -> Option<Scalar> {
81        let u = self.angle(t)?;
82        let p = self.slope(t)?;
83        let (s, c) = u.sin_cos();
84        let (a, b) = (self.a.value(t), self.b.value(t));
85        let (da, db) = (self.a.derivative(t), self.b.derivative(t));
86        let f_u = -a * s + b * c;
87        let f_tt = self.a.second(t) * c + self.b.second(t) * s - self.c.second(t);
88        let f_tu = -da * s + db * c;
89        let f_uu = -a * c - b * s;
90        let bend = -(f_tt + 2.0 * f_tu * p + f_uu * p * p) / f_u;
91        bend.is_finite().then_some(bend)
92    }
93
94    /// Whether every coefficient is finite.
95    #[must_use]
96    pub fn is_finite(&self) -> bool {
97        self.a.is_finite() && self.b.is_finite() && self.c.is_finite()
98    }
99}
100
101/// A torus, as the curve needs it, in `axiolid_surface::Torus`'s
102/// parameterisation: `O + (R + r cos v)(cos u X + sin u Y) + r sin v Z`.
103#[derive(Debug, Clone, Copy, PartialEq)]
104pub struct TorusCarrier {
105    /// Local frame; `z` is the axis.
106    pub frame: Frame3,
107    /// Distance from the axis to the tube centre.
108    pub major_radius: Scalar,
109    /// Tube radius.
110    pub minor_radius: Scalar,
111}
112
113impl TorusCarrier {
114    /// The torus point at `(u, v)`.
115    #[must_use]
116    pub fn point(&self, u: Scalar, v: Scalar) -> Point3 {
117        let (su, cu) = u.sin_cos();
118        let (sv, cv) = v.sin_cos();
119        let ring = self.major_radius + self.minor_radius * cv;
120        self.frame.origin
121            + self.frame.x * (ring * cu)
122            + self.frame.y * (ring * su)
123            + self.frame.z * (self.minor_radius * sv)
124    }
125
126    /// `(dP/du, dP/dv)`.
127    #[must_use]
128    pub fn partials(&self, u: Scalar, v: Scalar) -> (Vec3, Vec3) {
129        let (su, cu) = u.sin_cos();
130        let (sv, cv) = v.sin_cos();
131        let ring = self.major_radius + self.minor_radius * cv;
132        let r = self.minor_radius;
133        (
134            self.frame.x * (-ring * su) + self.frame.y * (ring * cu),
135            self.frame.x * (-r * sv * cu) + self.frame.y * (-r * sv * su) + self.frame.z * (r * cv),
136        )
137    }
138}
139
140/// A plane's or sphere's section of a torus, in space: the torus point at
141/// `(graph.angle(t), t)`.
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct TorusSection3 {
144    /// The torus the curve lies on.
145    pub torus: TorusCarrier,
146    /// The curve in the torus's parameters.
147    pub graph: AngleGraph2,
148}
149
150impl TorusSection3 {
151    /// Point at `t`.
152    #[must_use]
153    pub fn point(&self, t: Scalar) -> Option<Point3> {
154        Some(self.torus.point(self.graph.angle(t)?, t))
155    }
156
157    /// Tangent at `t`: `P_u du/dt + P_v`.
158    #[must_use]
159    pub fn tangent(&self, t: Scalar) -> Option<Vec3> {
160        let u = self.graph.angle(t)?;
161        let (pu, pv) = self.torus.partials(u, t);
162        Some(pu * self.graph.slope(t)? + pv)
163    }
164
165    /// Second derivative at `t`: `P_uu u'^2 + 2 P_uv u' + P_vv + P_u u''`.
166    #[must_use]
167    pub fn bend(&self, t: Scalar) -> Option<Vec3> {
168        let u = self.graph.angle(t)?;
169        let p = self.graph.slope(t)?;
170        let q = self.graph.bend(t)?;
171        let (su, cu) = u.sin_cos();
172        let (sv, cv) = t.sin_cos();
173        let (x, y, z) = (self.torus.frame.x, self.torus.frame.y, self.torus.frame.z);
174        let r = self.torus.minor_radius;
175        let ring = self.torus.major_radius + r * cv;
176        let p_u = x * (-ring * su) + y * (ring * cu);
177        let p_uu = x * (-ring * cu) + y * (-ring * su);
178        let p_uv = x * (r * sv * su) + y * (-r * sv * cu);
179        let p_vv = x * (-r * cv * cu) + y * (-r * cv * su) + z * (-r * sv);
180        Some(p_uu * (p * p) + p_uv * (2.0 * p) + p_vv + p_u * q)
181    }
182}