Skip to main content

proof_engine/geometry/
bezier.rs

1//! Bezier and NURBS surface rendering.
2
3use glam::{Vec2, Vec3, Vec4};
4use super::{GeoMesh, parametric::ParametricSurface};
5
6/// A 3D control point with optional weight (for NURBS).
7#[derive(Debug, Clone, Copy)]
8pub struct ControlPoint {
9    pub position: Vec3,
10    pub weight: f32,
11}
12
13impl ControlPoint {
14    pub fn new(pos: Vec3) -> Self { Self { position: pos, weight: 1.0 } }
15    pub fn weighted(pos: Vec3, w: f32) -> Self { Self { position: pos, weight: w } }
16}
17
18/// Bicubic Bezier surface patch (4×4 control points).
19#[derive(Debug, Clone)]
20pub struct BezierSurface {
21    pub control_points: [[ControlPoint; 4]; 4],
22}
23
24impl BezierSurface {
25    pub fn new(points: [[Vec3; 4]; 4]) -> Self {
26        let mut cp = [[ControlPoint::new(Vec3::ZERO); 4]; 4];
27        for i in 0..4 { for j in 0..4 { cp[i][j] = ControlPoint::new(points[i][j]); } }
28        Self { control_points: cp }
29    }
30
31    fn bernstein(t: f32) -> [f32; 4] {
32        let s = 1.0 - t;
33        [s * s * s, 3.0 * s * s * t, 3.0 * s * t * t, t * t * t]
34    }
35}
36
37impl ParametricSurface for BezierSurface {
38    fn evaluate(&self, u: f32, v: f32) -> Vec3 {
39        let bu = Self::bernstein(u);
40        let bv = Self::bernstein(v);
41        let mut pos = Vec3::ZERO;
42        for i in 0..4 {
43            for j in 0..4 {
44                pos += self.control_points[i][j].position * bu[i] * bv[j];
45            }
46        }
47        pos
48    }
49}
50
51/// NURBS surface of arbitrary degree.
52#[derive(Debug, Clone)]
53pub struct NurbsSurface {
54    pub control_points: Vec<Vec<ControlPoint>>,
55    pub knots_u: Vec<f32>,
56    pub knots_v: Vec<f32>,
57    pub degree_u: usize,
58    pub degree_v: usize,
59}
60
61impl NurbsSurface {
62    pub fn new(
63        control_points: Vec<Vec<ControlPoint>>,
64        knots_u: Vec<f32>, knots_v: Vec<f32>,
65        degree_u: usize, degree_v: usize,
66    ) -> Self {
67        Self { control_points, knots_u, knots_v, degree_u, degree_v }
68    }
69
70    fn basis(knots: &[f32], i: usize, degree: usize, t: f32) -> f32 {
71        if degree == 0 {
72            return if t >= knots[i] && t < knots[i + 1] { 1.0 } else { 0.0 };
73        }
74        let mut result = 0.0;
75        let d1 = knots[i + degree] - knots[i];
76        if d1.abs() > 1e-10 {
77            result += (t - knots[i]) / d1 * Self::basis(knots, i, degree - 1, t);
78        }
79        let d2 = knots[i + degree + 1] - knots[i + 1];
80        if d2.abs() > 1e-10 {
81            result += (knots[i + degree + 1] - t) / d2 * Self::basis(knots, i + 1, degree - 1, t);
82        }
83        result
84    }
85}
86
87impl ParametricSurface for NurbsSurface {
88    fn evaluate(&self, u: f32, v: f32) -> Vec3 {
89        let rows = self.control_points.len();
90        let cols = if rows > 0 { self.control_points[0].len() } else { 0 };
91
92        let mut numerator = Vec3::ZERO;
93        let mut denominator = 0.0;
94
95        for i in 0..rows {
96            let bu = Self::basis(&self.knots_u, i, self.degree_u, u.clamp(0.001, 0.999));
97            for j in 0..cols {
98                let bv = Self::basis(&self.knots_v, j, self.degree_v, v.clamp(0.001, 0.999));
99                let cp = &self.control_points[i][j];
100                let w = bu * bv * cp.weight;
101                numerator += cp.position * w;
102                denominator += w;
103            }
104        }
105
106        if denominator.abs() > 1e-10 { numerator / denominator } else { Vec3::ZERO }
107    }
108}
109
110/// Bezier curve (arbitrary degree).
111#[derive(Debug, Clone)]
112pub struct BezierCurve {
113    pub control_points: Vec<Vec3>,
114}
115
116impl BezierCurve {
117    pub fn new(points: Vec<Vec3>) -> Self { Self { control_points: points } }
118
119    /// Evaluate at parameter t ∈ [0, 1] using De Casteljau's algorithm.
120    pub fn evaluate(&self, t: f32) -> Vec3 {
121        let n = self.control_points.len();
122        if n == 0 { return Vec3::ZERO; }
123        let mut temp = self.control_points.clone();
124        for k in 1..n {
125            for i in 0..n - k {
126                temp[i] = temp[i] * (1.0 - t) + temp[i + 1] * t;
127            }
128        }
129        temp[0]
130    }
131
132    /// Tangent at parameter t.
133    pub fn tangent(&self, t: f32) -> Vec3 {
134        let eps = 1e-4;
135        (self.evaluate((t + eps).min(1.0)) - self.evaluate((t - eps).max(0.0))).normalize_or_zero()
136    }
137
138    /// Split curve at parameter t into two curves.
139    pub fn split(&self, t: f32) -> (BezierCurve, BezierCurve) {
140        let n = self.control_points.len();
141        let mut left = Vec::with_capacity(n);
142        let mut right = Vec::with_capacity(n);
143        let mut temp = self.control_points.clone();
144
145        left.push(temp[0]);
146        right.push(temp[n - 1]);
147
148        for k in 1..n {
149            for i in 0..n - k {
150                temp[i] = temp[i] * (1.0 - t) + temp[i + 1] * t;
151            }
152            left.push(temp[0]);
153            right.push(temp[n - 1 - k]);
154        }
155
156        right.reverse();
157        (BezierCurve::new(left), BezierCurve::new(right))
158    }
159
160    /// Arc length approximation.
161    pub fn arc_length(&self, segments: usize) -> f32 {
162        let mut length = 0.0;
163        let mut prev = self.evaluate(0.0);
164        for i in 1..=segments {
165            let t = i as f32 / segments as f32;
166            let p = self.evaluate(t);
167            length += (p - prev).length();
168            prev = p;
169        }
170        length
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn bezier_curve_endpoints() {
180        let curve = BezierCurve::new(vec![Vec3::ZERO, Vec3::X, Vec3::new(1.0, 1.0, 0.0)]);
181        assert!((curve.evaluate(0.0) - Vec3::ZERO).length() < 1e-5);
182        assert!((curve.evaluate(1.0) - Vec3::new(1.0, 1.0, 0.0)).length() < 1e-5);
183    }
184
185    #[test]
186    fn bezier_surface_evaluates() {
187        let points = [[Vec3::ZERO; 4]; 4];
188        let surface = BezierSurface::new(points);
189        let p = surface.evaluate(0.5, 0.5);
190        assert!((p - Vec3::ZERO).length() < 1e-5);
191    }
192
193    #[test]
194    fn bezier_split_preserves_curve() {
195        let curve = BezierCurve::new(vec![Vec3::ZERO, Vec3::new(1.0, 2.0, 0.0), Vec3::X * 2.0]);
196        let (left, right) = curve.split(0.5);
197        let mid_orig = curve.evaluate(0.5);
198        let mid_left = left.evaluate(1.0);
199        assert!((mid_orig - mid_left).length() < 1e-4);
200    }
201}