Skip to main content

brepkit_math/
traits.rs

1//! Parametric geometry traits for unified curve and surface evaluation.
2//!
3//! These traits provide a common interface for evaluating both analytic
4//! geometry types (circles, cylinders, etc.) and NURBS representations.
5
6use crate::curves::{Circle3D, Ellipse3D};
7use crate::nurbs::curve::NurbsCurve;
8use crate::nurbs::projection::project_point_to_surface;
9use crate::nurbs::surface::NurbsSurface;
10use crate::surfaces::{ConicalSurface, CylindricalSurface, SphericalSurface, ToroidalSurface};
11use crate::vec::{Point3, Vec3};
12
13/// Unified interface for parametric surface evaluation.
14///
15/// Implemented by analytic surfaces ([`CylindricalSurface`], [`ConicalSurface`],
16/// [`SphericalSurface`], [`ToroidalSurface`]) and [`NurbsSurface`].
17pub trait ParametricSurface {
18    /// Evaluate the surface at parameters `(u, v)`.
19    fn evaluate(&self, u: f64, v: f64) -> Point3;
20
21    /// Surface normal at parameters `(u, v)`.
22    ///
23    /// Returns the unit normal. For NURBS surfaces at degenerate points,
24    /// implementations should return a best-effort fallback (e.g. `Vec3::Z`).
25    fn normal(&self, u: f64, v: f64) -> Vec3;
26
27    /// Project a 3D point onto the surface, returning `(u, v)` parameters.
28    fn project_point(&self, point: Point3) -> (f64, f64);
29
30    /// Partial derivative ∂S/∂u at (u, v).
31    fn partial_u(&self, u: f64, v: f64) -> Vec3;
32
33    /// Partial derivative ∂S/∂v at (u, v).
34    fn partial_v(&self, u: f64, v: f64) -> Vec3;
35
36    /// The u range `(start, end)` of a surface that closes on itself across
37    /// it, so that u wraps round by `end - start`. `None` (the default) for
38    /// a surface open in u, or one whose evaluation is already periodic in
39    /// u (the analytic surfaces).
40    fn closed_u(&self) -> Option<(f64, f64)> {
41        None
42    }
43
44    /// The v range of a surface that closes on itself across it, as
45    /// [`Self::closed_u`] for u.
46    fn closed_v(&self) -> Option<(f64, f64)> {
47        None
48    }
49}
50
51/// Unified interface for parametric curve evaluation.
52///
53/// Implemented by analytic curves ([`Circle3D`], [`Ellipse3D`]) and [`NurbsCurve`].
54pub trait ParametricCurve {
55    /// Evaluate the curve at parameter `t`.
56    fn evaluate(&self, t: f64) -> Point3;
57
58    /// Tangent vector at parameter `t`.
59    fn tangent(&self, t: f64) -> Vec3;
60
61    /// Parameter domain as `(t_min, t_max)`.
62    fn domain(&self) -> (f64, f64);
63}
64
65// ── ParametricSurface implementations ────────────────────────────────
66
67impl ParametricSurface for CylindricalSurface {
68    #[inline]
69    fn evaluate(&self, u: f64, v: f64) -> Point3 {
70        self.evaluate(u, v)
71    }
72
73    #[inline]
74    fn normal(&self, u: f64, v: f64) -> Vec3 {
75        self.normal(u, v)
76    }
77
78    #[inline]
79    fn project_point(&self, point: Point3) -> (f64, f64) {
80        self.project_point(point)
81    }
82
83    #[inline]
84    fn partial_u(&self, u: f64, _v: f64) -> Vec3 {
85        let (sin_u, cos_u) = u.sin_cos();
86        self.x_axis() * (-self.radius() * sin_u) + self.y_axis() * (self.radius() * cos_u)
87    }
88
89    #[inline]
90    fn partial_v(&self, _u: f64, _v: f64) -> Vec3 {
91        self.axis()
92    }
93}
94
95impl ParametricSurface for ConicalSurface {
96    #[inline]
97    fn evaluate(&self, u: f64, v: f64) -> Point3 {
98        self.evaluate(u, v)
99    }
100
101    #[inline]
102    fn normal(&self, u: f64, v: f64) -> Vec3 {
103        self.normal(u, v)
104    }
105
106    #[inline]
107    fn project_point(&self, point: Point3) -> (f64, f64) {
108        self.project_point(point)
109    }
110
111    #[inline]
112    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
113        let (sin_u, cos_u) = u.sin_cos();
114        let cos_a = self.half_angle().cos();
115        self.x_axis() * (-v * cos_a * sin_u) + self.y_axis() * (v * cos_a * cos_u)
116    }
117
118    #[inline]
119    fn partial_v(&self, u: f64, _v: f64) -> Vec3 {
120        let (sin_u, cos_u) = u.sin_cos();
121        let (sin_a, cos_a) = self.half_angle().sin_cos();
122        (self.x_axis() * cos_u + self.y_axis() * sin_u) * cos_a + self.axis() * sin_a
123    }
124}
125
126impl ParametricSurface for SphericalSurface {
127    #[inline]
128    fn evaluate(&self, u: f64, v: f64) -> Point3 {
129        self.evaluate(u, v)
130    }
131
132    #[inline]
133    fn normal(&self, u: f64, v: f64) -> Vec3 {
134        self.normal(u, v)
135    }
136
137    #[inline]
138    fn project_point(&self, point: Point3) -> (f64, f64) {
139        self.project_point(point)
140    }
141
142    #[inline]
143    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
144        let (sin_u, cos_u) = u.sin_cos();
145        let cos_v = v.cos();
146        self.x_axis() * (-self.radius() * cos_v * sin_u)
147            + self.y_axis() * (self.radius() * cos_v * cos_u)
148    }
149
150    #[inline]
151    fn partial_v(&self, u: f64, v: f64) -> Vec3 {
152        let (sin_u, cos_u) = u.sin_cos();
153        let (sin_v, cos_v) = v.sin_cos();
154        self.x_axis() * (-self.radius() * sin_v * cos_u)
155            + self.y_axis() * (-self.radius() * sin_v * sin_u)
156            + self.z_axis() * (self.radius() * cos_v)
157    }
158}
159
160impl ParametricSurface for ToroidalSurface {
161    #[inline]
162    fn evaluate(&self, u: f64, v: f64) -> Point3 {
163        self.evaluate(u, v)
164    }
165
166    #[inline]
167    fn normal(&self, u: f64, v: f64) -> Vec3 {
168        self.normal(u, v)
169    }
170
171    #[inline]
172    fn project_point(&self, point: Point3) -> (f64, f64) {
173        self.project_point(point)
174    }
175
176    #[inline]
177    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
178        let (sin_u, cos_u) = u.sin_cos();
179        let cos_v = v.cos();
180        let tube_radius = self.major_radius() + self.minor_radius() * cos_v;
181        self.x_axis() * (-tube_radius * sin_u) + self.y_axis() * (tube_radius * cos_u)
182    }
183
184    #[inline]
185    fn partial_v(&self, u: f64, v: f64) -> Vec3 {
186        let (sin_u, cos_u) = u.sin_cos();
187        let (sin_v, cos_v) = v.sin_cos();
188        (self.x_axis() * cos_u + self.y_axis() * sin_u) * (-self.minor_radius() * sin_v)
189            + self.z_axis() * (self.minor_radius() * cos_v)
190    }
191}
192
193impl ParametricSurface for NurbsSurface {
194    #[inline]
195    fn evaluate(&self, u: f64, v: f64) -> Point3 {
196        self.evaluate(u, v)
197    }
198
199    #[inline]
200    fn normal(&self, u: f64, v: f64) -> Vec3 {
201        self.normal(u, v)
202            .unwrap_or_else(|_| Vec3::new(0.0, 0.0, 1.0))
203    }
204
205    #[inline]
206    fn project_point(&self, point: Point3) -> (f64, f64) {
207        // Use default linear tolerance (1e-7) for the Newton projection.
208        if let Ok(proj) = project_point_to_surface(self, point, 1e-7) {
209            (proj.u, proj.v)
210        } else {
211            // Fallback: return domain midpoint if Newton fails.
212            let (u0, u1) = self.domain_u();
213            let (v0, v1) = self.domain_v();
214            ((u0 + u1) * 0.5, (v0 + v1) * 0.5)
215        }
216    }
217
218    #[inline]
219    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
220        let d = self.derivatives(u, v, 1);
221        d[1][0]
222    }
223
224    #[inline]
225    fn partial_v(&self, u: f64, v: f64) -> Vec3 {
226        let d = self.derivatives(u, v, 1);
227        d[0][1]
228    }
229
230    fn closed_u(&self) -> Option<(f64, f64)> {
231        self.is_periodic_u().then(|| self.domain_u())
232    }
233
234    fn closed_v(&self) -> Option<(f64, f64)> {
235        self.is_periodic_v().then(|| self.domain_v())
236    }
237}
238
239// ── ParametricCurve implementations ─────────────────────────────────
240
241impl ParametricCurve for Circle3D {
242    #[inline]
243    fn evaluate(&self, t: f64) -> Point3 {
244        self.evaluate(t)
245    }
246
247    #[inline]
248    fn tangent(&self, t: f64) -> Vec3 {
249        self.tangent(t)
250    }
251
252    #[inline]
253    fn domain(&self) -> (f64, f64) {
254        (0.0, std::f64::consts::TAU)
255    }
256}
257
258impl ParametricCurve for Ellipse3D {
259    #[inline]
260    fn evaluate(&self, t: f64) -> Point3 {
261        self.evaluate(t)
262    }
263
264    #[inline]
265    fn tangent(&self, t: f64) -> Vec3 {
266        self.tangent(t)
267    }
268
269    #[inline]
270    fn domain(&self) -> (f64, f64) {
271        (0.0, std::f64::consts::TAU)
272    }
273}
274
275impl ParametricCurve for NurbsCurve {
276    #[inline]
277    fn evaluate(&self, t: f64) -> Point3 {
278        self.evaluate(t)
279    }
280
281    #[inline]
282    fn tangent(&self, t: f64) -> Vec3 {
283        self.tangent(t).unwrap_or_else(|_| Vec3::new(1.0, 0.0, 0.0))
284    }
285
286    #[inline]
287    fn domain(&self) -> (f64, f64) {
288        self.domain()
289    }
290}