Skip to main content

agg_rust/
ellipse.rs

1//! Ellipse vertex generator.
2//!
3//! Port of `agg_ellipse.h` — generates vertices approximating an ellipse
4//! as a regular polygon, suitable for use as a VertexSource.
5
6use crate::basics::{
7    uround, VertexSource, PATH_CMD_END_POLY, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP,
8    PATH_FLAGS_CCW, PATH_FLAGS_CLOSE, PI,
9};
10
11/// Ellipse vertex generator.
12///
13/// Generates a closed polygon approximating an ellipse. The number of
14/// steps is either specified explicitly or calculated automatically from
15/// the approximation scale.
16///
17/// Port of C++ `agg::ellipse`.
18pub struct Ellipse {
19    x: f64,
20    y: f64,
21    rx: f64,
22    ry: f64,
23    scale: f64,
24    num: u32,
25    step: u32,
26    cw: bool,
27}
28
29impl Ellipse {
30    /// Create a new ellipse with automatic step calculation.
31    pub fn new(x: f64, y: f64, rx: f64, ry: f64, num_steps: u32, cw: bool) -> Self {
32        let mut e = Self {
33            x,
34            y,
35            rx,
36            ry,
37            scale: 1.0,
38            num: num_steps,
39            step: 0,
40            cw,
41        };
42        if e.num == 0 {
43            e.calc_num_steps();
44        }
45        e
46    }
47
48    /// Create a default ellipse (unit circle at origin).
49    pub fn default_new() -> Self {
50        Self {
51            x: 0.0,
52            y: 0.0,
53            rx: 1.0,
54            ry: 1.0,
55            scale: 1.0,
56            num: 4,
57            step: 0,
58            cw: false,
59        }
60    }
61
62    /// Re-initialize with new parameters.
63    pub fn init(&mut self, x: f64, y: f64, rx: f64, ry: f64, num_steps: u32, cw: bool) {
64        self.x = x;
65        self.y = y;
66        self.rx = rx;
67        self.ry = ry;
68        self.num = num_steps;
69        self.step = 0;
70        self.cw = cw;
71        if self.num == 0 {
72            self.calc_num_steps();
73        }
74    }
75
76    /// Set approximation scale (affects automatic step count).
77    pub fn set_approximation_scale(&mut self, scale: f64) {
78        self.scale = scale;
79        self.calc_num_steps();
80    }
81
82    /// Calculate step count from radii and approximation scale.
83    fn calc_num_steps(&mut self) {
84        let ra = (self.rx.abs() + self.ry.abs()) / 2.0;
85        let da = (ra / (ra + 0.125 / self.scale)).acos() * 2.0;
86        self.num = uround(2.0 * PI / da);
87    }
88}
89
90impl VertexSource for Ellipse {
91    fn rewind(&mut self, _path_id: u32) {
92        self.step = 0;
93    }
94
95    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
96        if self.step == self.num {
97            self.step += 1;
98            return PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CCW;
99        }
100        if self.step > self.num {
101            return PATH_CMD_STOP;
102        }
103        let mut angle = self.step as f64 / self.num as f64 * 2.0 * PI;
104        if self.cw {
105            angle = 2.0 * PI - angle;
106        }
107        *x = self.x + angle.cos() * self.rx;
108        *y = self.y + angle.sin() * self.ry;
109        self.step += 1;
110        if self.step == 1 {
111            PATH_CMD_MOVE_TO
112        } else {
113            PATH_CMD_LINE_TO
114        }
115    }
116}
117
118// ============================================================================
119// Tests
120// ============================================================================
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::basics::{is_end_poly, is_stop};
126
127    #[test]
128    fn test_ellipse_basic() {
129        let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 8, false);
130        e.rewind(0);
131        let mut x = 0.0;
132        let mut y = 0.0;
133
134        // First vertex = move_to at angle 0
135        let cmd = e.vertex(&mut x, &mut y);
136        assert_eq!(cmd, PATH_CMD_MOVE_TO);
137        assert!((x - 10.0).abs() < 1e-6);
138        assert!(y.abs() < 1e-6);
139
140        // Next 7 vertices = line_to
141        for _ in 1..8 {
142            let cmd = e.vertex(&mut x, &mut y);
143            assert_eq!(cmd, PATH_CMD_LINE_TO);
144        }
145
146        // Close polygon
147        let cmd = e.vertex(&mut x, &mut y);
148        assert!(is_end_poly(cmd));
149
150        // Stop
151        let cmd = e.vertex(&mut x, &mut y);
152        assert!(is_stop(cmd));
153    }
154
155    #[test]
156    fn test_ellipse_vertices_on_circle() {
157        let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 4, false);
158        e.rewind(0);
159        let mut x = 0.0;
160        let mut y = 0.0;
161
162        // Vertex 0: (10, 0)
163        e.vertex(&mut x, &mut y);
164        assert!((x - 10.0).abs() < 1e-6);
165        assert!(y.abs() < 1e-6);
166
167        // Vertex 1: (0, 10)
168        e.vertex(&mut x, &mut y);
169        assert!(x.abs() < 1e-6);
170        assert!((y - 10.0).abs() < 1e-6);
171
172        // Vertex 2: (-10, 0)
173        e.vertex(&mut x, &mut y);
174        assert!((x + 10.0).abs() < 1e-6);
175        assert!(y.abs() < 1e-6);
176
177        // Vertex 3: (0, -10)
178        e.vertex(&mut x, &mut y);
179        assert!(x.abs() < 1e-6);
180        assert!((y + 10.0).abs() < 1e-6);
181    }
182
183    #[test]
184    fn test_ellipse_cw() {
185        let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 4, true);
186        e.rewind(0);
187        let mut x = 0.0;
188        let mut y = 0.0;
189
190        // Vertex 0: still (10, 0) — angle 0
191        e.vertex(&mut x, &mut y);
192        assert!((x - 10.0).abs() < 1e-6);
193
194        // Vertex 1: CW = (0, -10) instead of (0, 10)
195        e.vertex(&mut x, &mut y);
196        assert!(x.abs() < 1e-6);
197        assert!((y + 10.0).abs() < 1e-6);
198    }
199
200    #[test]
201    fn test_ellipse_center_offset() {
202        let mut e = Ellipse::new(5.0, 3.0, 10.0, 10.0, 4, false);
203        e.rewind(0);
204        let mut x = 0.0;
205        let mut y = 0.0;
206
207        e.vertex(&mut x, &mut y);
208        assert!((x - 15.0).abs() < 1e-6);
209        assert!((y - 3.0).abs() < 1e-6);
210    }
211
212    #[test]
213    fn test_ellipse_auto_steps() {
214        let e = Ellipse::new(0.0, 0.0, 100.0, 100.0, 0, false);
215        // Auto-calculated steps should be reasonable for r=100
216        assert!(e.num > 20);
217    }
218
219    #[test]
220    fn test_ellipse_rewind_restarts() {
221        let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 4, false);
222        let mut x = 0.0;
223        let mut y = 0.0;
224
225        // Consume some vertices
226        e.rewind(0);
227        e.vertex(&mut x, &mut y);
228        e.vertex(&mut x, &mut y);
229
230        // Rewind should restart
231        e.rewind(0);
232        let cmd = e.vertex(&mut x, &mut y);
233        assert_eq!(cmd, PATH_CMD_MOVE_TO);
234        assert!((x - 10.0).abs() < 1e-6);
235    }
236
237    #[test]
238    fn test_ellipse_different_radii() {
239        let mut e = Ellipse::new(0.0, 0.0, 20.0, 10.0, 4, false);
240        e.rewind(0);
241        let mut x = 0.0;
242        let mut y = 0.0;
243
244        // Vertex 0: (20, 0) — rx
245        e.vertex(&mut x, &mut y);
246        assert!((x - 20.0).abs() < 1e-6);
247
248        // Vertex 1: (0, 10) — ry
249        e.vertex(&mut x, &mut y);
250        assert!(x.abs() < 1e-6);
251        assert!((y - 10.0).abs() < 1e-6);
252    }
253}