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
37/// Unified interface for parametric curve evaluation.
38///
39/// Implemented by analytic curves ([`Circle3D`], [`Ellipse3D`]) and [`NurbsCurve`].
40pub trait ParametricCurve {
41    /// Evaluate the curve at parameter `t`.
42    fn evaluate(&self, t: f64) -> Point3;
43
44    /// Tangent vector at parameter `t`.
45    fn tangent(&self, t: f64) -> Vec3;
46
47    /// Parameter domain as `(t_min, t_max)`.
48    fn domain(&self) -> (f64, f64);
49}
50
51// ── ParametricSurface implementations ────────────────────────────────
52
53impl ParametricSurface for CylindricalSurface {
54    #[inline]
55    fn evaluate(&self, u: f64, v: f64) -> Point3 {
56        self.evaluate(u, v)
57    }
58
59    #[inline]
60    fn normal(&self, u: f64, v: f64) -> Vec3 {
61        self.normal(u, v)
62    }
63
64    #[inline]
65    fn project_point(&self, point: Point3) -> (f64, f64) {
66        self.project_point(point)
67    }
68
69    #[inline]
70    fn partial_u(&self, u: f64, _v: f64) -> Vec3 {
71        let (sin_u, cos_u) = u.sin_cos();
72        self.x_axis() * (-self.radius() * sin_u) + self.y_axis() * (self.radius() * cos_u)
73    }
74
75    #[inline]
76    fn partial_v(&self, _u: f64, _v: f64) -> Vec3 {
77        self.axis()
78    }
79}
80
81impl ParametricSurface for ConicalSurface {
82    #[inline]
83    fn evaluate(&self, u: f64, v: f64) -> Point3 {
84        self.evaluate(u, v)
85    }
86
87    #[inline]
88    fn normal(&self, u: f64, v: f64) -> Vec3 {
89        self.normal(u, v)
90    }
91
92    #[inline]
93    fn project_point(&self, point: Point3) -> (f64, f64) {
94        self.project_point(point)
95    }
96
97    #[inline]
98    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
99        let (sin_u, cos_u) = u.sin_cos();
100        let cos_a = self.half_angle().cos();
101        self.x_axis() * (-v * cos_a * sin_u) + self.y_axis() * (v * cos_a * cos_u)
102    }
103
104    #[inline]
105    fn partial_v(&self, u: f64, _v: f64) -> Vec3 {
106        let (sin_u, cos_u) = u.sin_cos();
107        let (sin_a, cos_a) = self.half_angle().sin_cos();
108        (self.x_axis() * cos_u + self.y_axis() * sin_u) * cos_a + self.axis() * sin_a
109    }
110}
111
112impl ParametricSurface for SphericalSurface {
113    #[inline]
114    fn evaluate(&self, u: f64, v: f64) -> Point3 {
115        self.evaluate(u, v)
116    }
117
118    #[inline]
119    fn normal(&self, u: f64, v: f64) -> Vec3 {
120        self.normal(u, v)
121    }
122
123    #[inline]
124    fn project_point(&self, point: Point3) -> (f64, f64) {
125        self.project_point(point)
126    }
127
128    #[inline]
129    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
130        let (sin_u, cos_u) = u.sin_cos();
131        let cos_v = v.cos();
132        self.x_axis() * (-self.radius() * cos_v * sin_u)
133            + self.y_axis() * (self.radius() * cos_v * cos_u)
134    }
135
136    #[inline]
137    fn partial_v(&self, u: f64, v: f64) -> Vec3 {
138        let (sin_u, cos_u) = u.sin_cos();
139        let (sin_v, cos_v) = v.sin_cos();
140        self.x_axis() * (-self.radius() * sin_v * cos_u)
141            + self.y_axis() * (-self.radius() * sin_v * sin_u)
142            + self.z_axis() * (self.radius() * cos_v)
143    }
144}
145
146impl ParametricSurface for ToroidalSurface {
147    #[inline]
148    fn evaluate(&self, u: f64, v: f64) -> Point3 {
149        self.evaluate(u, v)
150    }
151
152    #[inline]
153    fn normal(&self, u: f64, v: f64) -> Vec3 {
154        self.normal(u, v)
155    }
156
157    #[inline]
158    fn project_point(&self, point: Point3) -> (f64, f64) {
159        self.project_point(point)
160    }
161
162    #[inline]
163    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
164        let (sin_u, cos_u) = u.sin_cos();
165        let cos_v = v.cos();
166        let tube_radius = self.major_radius() + self.minor_radius() * cos_v;
167        self.x_axis() * (-tube_radius * sin_u) + self.y_axis() * (tube_radius * cos_u)
168    }
169
170    #[inline]
171    fn partial_v(&self, u: f64, v: f64) -> Vec3 {
172        let (sin_u, cos_u) = u.sin_cos();
173        let (sin_v, cos_v) = v.sin_cos();
174        (self.x_axis() * cos_u + self.y_axis() * sin_u) * (-self.minor_radius() * sin_v)
175            + self.z_axis() * (self.minor_radius() * cos_v)
176    }
177}
178
179impl ParametricSurface for NurbsSurface {
180    #[inline]
181    fn evaluate(&self, u: f64, v: f64) -> Point3 {
182        self.evaluate(u, v)
183    }
184
185    #[inline]
186    fn normal(&self, u: f64, v: f64) -> Vec3 {
187        self.normal(u, v)
188            .unwrap_or_else(|_| Vec3::new(0.0, 0.0, 1.0))
189    }
190
191    #[inline]
192    fn project_point(&self, point: Point3) -> (f64, f64) {
193        // Use default linear tolerance (1e-7) for the Newton projection.
194        if let Ok(proj) = project_point_to_surface(self, point, 1e-7) {
195            (proj.u, proj.v)
196        } else {
197            // Fallback: return domain midpoint if Newton fails.
198            let (u0, u1) = self.domain_u();
199            let (v0, v1) = self.domain_v();
200            ((u0 + u1) * 0.5, (v0 + v1) * 0.5)
201        }
202    }
203
204    #[inline]
205    fn partial_u(&self, u: f64, v: f64) -> Vec3 {
206        let d = self.derivatives(u, v, 1);
207        d[1][0]
208    }
209
210    #[inline]
211    fn partial_v(&self, u: f64, v: f64) -> Vec3 {
212        let d = self.derivatives(u, v, 1);
213        d[0][1]
214    }
215}
216
217// ── ParametricCurve implementations ─────────────────────────────────
218
219impl ParametricCurve for Circle3D {
220    #[inline]
221    fn evaluate(&self, t: f64) -> Point3 {
222        self.evaluate(t)
223    }
224
225    #[inline]
226    fn tangent(&self, t: f64) -> Vec3 {
227        self.tangent(t)
228    }
229
230    #[inline]
231    fn domain(&self) -> (f64, f64) {
232        (0.0, std::f64::consts::TAU)
233    }
234}
235
236impl ParametricCurve for Ellipse3D {
237    #[inline]
238    fn evaluate(&self, t: f64) -> Point3 {
239        self.evaluate(t)
240    }
241
242    #[inline]
243    fn tangent(&self, t: f64) -> Vec3 {
244        self.tangent(t)
245    }
246
247    #[inline]
248    fn domain(&self) -> (f64, f64) {
249        (0.0, std::f64::consts::TAU)
250    }
251}
252
253impl ParametricCurve for NurbsCurve {
254    #[inline]
255    fn evaluate(&self, t: f64) -> Point3 {
256        self.evaluate(t)
257    }
258
259    #[inline]
260    fn tangent(&self, t: f64) -> Vec3 {
261        self.tangent(t).unwrap_or_else(|_| Vec3::new(1.0, 0.0, 0.0))
262    }
263
264    #[inline]
265    fn domain(&self) -> (f64, f64) {
266        self.domain()
267    }
268}