Skip to main content

agg_rust/
vpgen_segmentator.rs

1//! Vertex processor generator: line segment subdivider.
2//!
3//! Port of `agg_vpgen_segmentator.h` + `agg_vpgen_segmentator.cpp`.
4//! Subdivides long line segments into shorter ones for better curve
5//! approximation when applying non-linear transforms.
6
7use crate::basics::{PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP};
8use crate::conv_adaptor_vpgen::VpgenProcessor;
9
10/// Subdivides line segments based on approximation scale.
11///
12/// Used by `ConvAdaptorVpgen` / `ConvSegmentator` to ensure no segment
13/// exceeds `1/approximation_scale` in length, which is needed for
14/// accurate rendering of non-linear transforms.
15pub struct VpgenSegmentator {
16    approximation_scale: f64,
17    x1: f64,
18    y1: f64,
19    dx: f64,
20    dy: f64,
21    dl: f64,
22    ddl: f64,
23    cmd: u32,
24}
25
26impl VpgenSegmentator {
27    pub fn new() -> Self {
28        Self {
29            approximation_scale: 1.0,
30            x1: 0.0,
31            y1: 0.0,
32            dx: 0.0,
33            dy: 0.0,
34            dl: 2.0,
35            ddl: 2.0,
36            cmd: PATH_CMD_STOP,
37        }
38    }
39
40    pub fn approximation_scale(&self) -> f64 {
41        self.approximation_scale
42    }
43
44    pub fn set_approximation_scale(&mut self, s: f64) {
45        self.approximation_scale = s;
46    }
47
48    pub fn auto_close() -> bool {
49        false
50    }
51
52    pub fn auto_unclose() -> bool {
53        false
54    }
55
56    pub fn reset(&mut self) {
57        self.cmd = PATH_CMD_STOP;
58    }
59
60    pub fn move_to(&mut self, x: f64, y: f64) {
61        self.x1 = x;
62        self.y1 = y;
63        self.dx = 0.0;
64        self.dy = 0.0;
65        self.dl = 2.0;
66        self.ddl = 2.0;
67        self.cmd = PATH_CMD_MOVE_TO;
68    }
69
70    pub fn line_to(&mut self, x: f64, y: f64) {
71        self.x1 += self.dx;
72        self.y1 += self.dy;
73        self.dx = x - self.x1;
74        self.dy = y - self.y1;
75        let mut len = (self.dx * self.dx + self.dy * self.dy).sqrt() * self.approximation_scale;
76        if len < 1e-30 {
77            len = 1e-30;
78        }
79        self.ddl = 1.0 / len;
80        self.dl = if self.cmd == PATH_CMD_MOVE_TO {
81            0.0
82        } else {
83            self.ddl
84        };
85        if self.cmd == PATH_CMD_STOP {
86            self.cmd = PATH_CMD_LINE_TO;
87        }
88    }
89
90    pub fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
91        if self.cmd == PATH_CMD_STOP {
92            return PATH_CMD_STOP;
93        }
94
95        let cmd = self.cmd;
96        self.cmd = PATH_CMD_LINE_TO;
97        if self.dl >= 1.0 - self.ddl {
98            self.dl = 1.0;
99            self.cmd = PATH_CMD_STOP;
100            *x = self.x1 + self.dx;
101            *y = self.y1 + self.dy;
102            return cmd;
103        }
104        *x = self.x1 + self.dx * self.dl;
105        *y = self.y1 + self.dy * self.dl;
106        self.dl += self.ddl;
107        cmd
108    }
109}
110
111impl VpgenProcessor for VpgenSegmentator {
112    fn reset(&mut self) {
113        self.reset();
114    }
115
116    fn move_to(&mut self, x: f64, y: f64) {
117        self.move_to(x, y);
118    }
119
120    fn line_to(&mut self, x: f64, y: f64) {
121        self.line_to(x, y);
122    }
123
124    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
125        self.vertex(x, y)
126    }
127
128    fn auto_close() -> bool {
129        VpgenSegmentator::auto_close()
130    }
131
132    fn auto_unclose() -> bool {
133        VpgenSegmentator::auto_unclose()
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_short_segment_no_subdivision() {
143        let mut vpgen = VpgenSegmentator::new();
144        vpgen.set_approximation_scale(1.0);
145        vpgen.move_to(0.0, 0.0);
146        vpgen.line_to(0.5, 0.0);
147
148        let (mut x, mut y) = (0.0, 0.0);
149        let cmd1 = vpgen.vertex(&mut x, &mut y);
150        assert_eq!(cmd1, PATH_CMD_MOVE_TO);
151        // Short segment (len*scale < 1): entire segment consumed in one step
152        assert!((x - 0.5).abs() < 1e-10);
153        assert!((y - 0.0).abs() < 1e-10);
154
155        // No more vertices — segment already complete
156        let cmd2 = vpgen.vertex(&mut x, &mut y);
157        assert_eq!(cmd2, PATH_CMD_STOP);
158    }
159
160    #[test]
161    fn test_long_segment_subdivision() {
162        let mut vpgen = VpgenSegmentator::new();
163        vpgen.set_approximation_scale(10.0); // force many subdivisions
164        vpgen.move_to(0.0, 0.0);
165        vpgen.line_to(100.0, 0.0);
166
167        let (mut x, mut y) = (0.0, 0.0);
168        let mut count = 0;
169        loop {
170            let cmd = vpgen.vertex(&mut x, &mut y);
171            if cmd == PATH_CMD_STOP {
172                break;
173            }
174            count += 1;
175        }
176        assert!(count > 2, "Long segment should be subdivided: count={count}");
177    }
178}