1use glam::{Vec2, Vec3, Vec4};
4use super::GeoMesh;
5
6pub trait ParametricSurface: Send + Sync {
8 fn evaluate(&self, u: f32, v: f32) -> Vec3;
10
11 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 fn color(&self, _u: f32, _v: f32) -> Vec4 { Vec4::ONE }
21}
22
23#[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#[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 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 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(); 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
85pub 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
102pub 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
117pub 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
142pub 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
158pub 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 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 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
188pub 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#[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); 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}