Skip to main content

proof_engine/geometry/
parametric.rs

1//! Parametric surface evaluation — any f(u,v) → Vec3 mapped to glyph grids.
2
3use glam::{Vec2, Vec3, Vec4};
4use super::GeoMesh;
5
6/// A parametric surface defined by a function f(u,v) → position.
7pub trait ParametricSurface: Send + Sync {
8    /// Evaluate the surface position at parameters (u, v) ∈ [0, 1]².
9    fn evaluate(&self, u: f32, v: f32) -> Vec3;
10
11    /// Surface normal at (u, v), computed via finite differences if not overridden.
12    fn normal(&self, u: f32, v: f32) -> Vec3 {
13        let eps = 1e-4;
14        let du = self.evaluate(u + eps, v) - self.evaluate(u - eps, v);
15        let dv = self.evaluate(u, v + eps) - self.evaluate(u, v - eps);
16        du.cross(dv).normalize_or_zero()
17    }
18
19    /// Optional color at (u, v).
20    fn color(&self, _u: f32, _v: f32) -> Vec4 { Vec4::ONE }
21}
22
23/// Sample point on a parametric surface.
24#[derive(Debug, Clone)]
25pub struct SurfaceSample {
26    pub position: Vec3,
27    pub normal: Vec3,
28    pub uv: Vec2,
29    pub color: Vec4,
30}
31
32/// A grid of samples from a parametric surface.
33#[derive(Debug, Clone)]
34pub struct SurfaceGrid {
35    pub samples: Vec<SurfaceSample>,
36    pub u_count: usize,
37    pub v_count: usize,
38}
39
40impl SurfaceGrid {
41    /// Sample a parametric surface on a regular u×v grid.
42    pub fn sample(surface: &dyn ParametricSurface, u_count: usize, v_count: usize) -> Self {
43        let mut samples = Vec::with_capacity(u_count * v_count);
44        for vi in 0..v_count {
45            let v = vi as f32 / (v_count - 1).max(1) as f32;
46            for ui in 0..u_count {
47                let u = ui as f32 / (u_count - 1).max(1) as f32;
48                samples.push(SurfaceSample {
49                    position: surface.evaluate(u, v),
50                    normal: surface.normal(u, v),
51                    uv: Vec2::new(u, v),
52                    color: surface.color(u, v),
53                });
54            }
55        }
56        Self { samples, u_count, v_count }
57    }
58
59    /// Convert to a triangle mesh.
60    pub fn to_mesh(&self) -> GeoMesh {
61        let mut mesh = GeoMesh::new();
62        for s in &self.samples {
63            mesh.add_vertex(s.position, s.normal, s.uv);
64            mesh.colors.pop(); // remove default white
65            mesh.colors.push(s.color);
66        }
67        for vi in 0..self.v_count - 1 {
68            for ui in 0..self.u_count - 1 {
69                let i00 = (vi * self.u_count + ui) as u32;
70                let i10 = i00 + 1;
71                let i01 = i00 + self.u_count as u32;
72                let i11 = i01 + 1;
73                mesh.add_triangle(i00, i10, i11);
74                mesh.add_triangle(i00, i11, i01);
75            }
76        }
77        mesh
78    }
79
80    pub fn get(&self, u_idx: usize, v_idx: usize) -> &SurfaceSample {
81        &self.samples[v_idx * self.u_count + u_idx]
82    }
83}
84
85// ── Built-in parametric surfaces ────────────────────────────────────────────
86
87/// Sphere of given radius.
88pub struct Sphere { pub radius: f32 }
89
90impl ParametricSurface for Sphere {
91    fn evaluate(&self, u: f32, v: f32) -> Vec3 {
92        let theta = u * std::f32::consts::TAU;
93        let phi = v * std::f32::consts::PI;
94        Vec3::new(
95            self.radius * phi.sin() * theta.cos(),
96            self.radius * phi.cos(),
97            self.radius * phi.sin() * theta.sin(),
98        )
99    }
100}
101
102/// Torus with major radius R and minor radius r.
103pub struct Torus { pub major: f32, pub minor: f32 }
104
105impl ParametricSurface for Torus {
106    fn evaluate(&self, u: f32, v: f32) -> Vec3 {
107        let theta = u * std::f32::consts::TAU;
108        let phi = v * std::f32::consts::TAU;
109        Vec3::new(
110            (self.major + self.minor * phi.cos()) * theta.cos(),
111            self.minor * phi.sin(),
112            (self.major + self.minor * phi.cos()) * theta.sin(),
113        )
114    }
115}
116
117/// Klein bottle (immersed in 3D).
118pub struct KleinBottle { pub scale: f32 }
119
120impl ParametricSurface for KleinBottle {
121    fn evaluate(&self, u: f32, v: f32) -> Vec3 {
122        let u = u * std::f32::consts::TAU;
123        let v = v * std::f32::consts::TAU;
124        let s = self.scale;
125        let r = 4.0 * (1.0 - (u / 2.0).cos());
126        if u < std::f32::consts::PI {
127            Vec3::new(
128                s * (6.0 * (1.0 + u.sin()) + r * (u / 2.0).cos() * v.cos()),
129                s * 16.0 * u.sin(),
130                s * r * v.sin(),
131            )
132        } else {
133            Vec3::new(
134                s * (6.0 * (1.0 + u.sin()) - r * (u / 2.0).cos() * v.cos()),
135                s * 16.0 * u.sin(),
136                s * r * v.sin(),
137            )
138        }
139    }
140}
141
142/// Möbius strip.
143pub struct MobiusStrip { pub radius: f32, pub width: f32 }
144
145impl ParametricSurface for MobiusStrip {
146    fn evaluate(&self, u: f32, v: f32) -> Vec3 {
147        let theta = u * std::f32::consts::TAU;
148        let s = (v - 0.5) * self.width;
149        let half_twist = theta / 2.0;
150        Vec3::new(
151            (self.radius + s * half_twist.cos()) * theta.cos(),
152            (self.radius + s * half_twist.cos()) * theta.sin(),
153            s * half_twist.sin(),
154        )
155    }
156}
157
158/// Trefoil knot tube.
159pub struct TrefoilKnot { pub radius: f32, pub tube: f32 }
160
161impl ParametricSurface for TrefoilKnot {
162    fn evaluate(&self, u: f32, v: f32) -> Vec3 {
163        let t = u * std::f32::consts::TAU;
164        let phi = v * std::f32::consts::TAU;
165        let r = self.radius;
166
167        // Trefoil curve
168        let cx = r * ((2.0 + (3.0 * t).cos()) * t.cos());
169        let cy = r * ((2.0 + (3.0 * t).cos()) * t.sin());
170        let cz = r * (3.0 * t).sin();
171
172        // Tangent and normal frame (Frenet)
173        let eps = 0.001;
174        let t2 = t + eps;
175        let cx2 = r * ((2.0 + (3.0 * t2).cos()) * t2.cos());
176        let cy2 = r * ((2.0 + (3.0 * t2).cos()) * t2.sin());
177        let cz2 = r * (3.0 * t2).sin();
178        let tangent = Vec3::new(cx2 - cx, cy2 - cy, cz2 - cz).normalize_or_zero();
179        let up = Vec3::Y;
180        let binormal = tangent.cross(up).normalize_or_zero();
181        let normal = binormal.cross(tangent).normalize_or_zero();
182
183        let offset = binormal * (self.tube * phi.cos()) + normal * (self.tube * phi.sin());
184        Vec3::new(cx, cy, cz) + offset
185    }
186}
187
188/// Custom surface from a closure.
189pub struct CustomSurface<F: Fn(f32, f32) -> Vec3 + Send + Sync> {
190    pub func: F,
191}
192
193impl<F: Fn(f32, f32) -> Vec3 + Send + Sync> ParametricSurface for CustomSurface<F> {
194    fn evaluate(&self, u: f32, v: f32) -> Vec3 { (self.func)(u, v) }
195}
196
197// ── Tests ───────────────────────────────────────────────────────────────────
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn sphere_radius() {
205        let s = Sphere { radius: 2.0 };
206        let p = s.evaluate(0.0, 0.5); // equator
207        assert!((p.length() - 2.0).abs() < 0.01);
208    }
209
210    #[test]
211    fn torus_topology() {
212        let t = Torus { major: 3.0, minor: 1.0 };
213        let grid = SurfaceGrid::sample(&t, 16, 16);
214        let mesh = grid.to_mesh();
215        assert!(mesh.vertex_count() > 0);
216        assert!(mesh.triangle_count() > 0);
217    }
218
219    #[test]
220    fn klein_bottle_generates() {
221        let k = KleinBottle { scale: 1.0 };
222        let grid = SurfaceGrid::sample(&k, 10, 10);
223        assert_eq!(grid.samples.len(), 100);
224    }
225
226    #[test]
227    fn mobius_strip_generates() {
228        let m = MobiusStrip { radius: 2.0, width: 1.0 };
229        let p = m.evaluate(0.0, 0.5);
230        assert!((p.length() - 2.0).abs() < 0.1);
231    }
232
233    #[test]
234    fn surface_grid_mesh_has_correct_counts() {
235        let s = Sphere { radius: 1.0 };
236        let grid = SurfaceGrid::sample(&s, 8, 8);
237        let mesh = grid.to_mesh();
238        assert_eq!(mesh.vertex_count(), 64);
239        assert_eq!(mesh.triangle_count(), 7 * 7 * 2);
240    }
241}