1 | //! Determine whether a point is inside a path. |
2 | |
3 | use crate::geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment}; |
4 | use crate::math::Point; |
5 | use crate::path::{FillRule, PathEvent}; |
6 | |
7 | /// Returns whether the point is inside the path. |
8 | pub fn hit_test_path<Iter>(point: &Point, path: Iter, fill_rule: FillRule, tolerance: f32) -> bool |
9 | where |
10 | Iter: IntoIterator<Item = PathEvent>, |
11 | { |
12 | let winding: i32 = path_winding_number_at_position(point, path, tolerance); |
13 | |
14 | match fill_rule { |
15 | FillRule::EvenOdd => winding % 2 != 0, |
16 | FillRule::NonZero => winding != 0, |
17 | } |
18 | } |
19 | |
20 | /// Compute the winding number of a given position with respect to the path. |
21 | pub fn path_winding_number_at_position<Iter>(point: &Point, path: Iter, tolerance: f32) -> i32 |
22 | where |
23 | Iter: IntoIterator<Item = PathEvent>, |
24 | { |
25 | // Loop over the edges and compute the winding number at that point by accumulating the |
26 | // winding of all edges intersecting the horizontal line passing through our point which are |
27 | // left of it. |
28 | let mut winding = 0; |
29 | |
30 | for evt in path { |
31 | match evt { |
32 | PathEvent::Begin { .. } => {} |
33 | PathEvent::Line { from, to } => { |
34 | test_segment( |
35 | *point, |
36 | &LineSegment { from, to }, |
37 | &mut winding, |
38 | ); |
39 | } |
40 | PathEvent::End { last, first, .. } => { |
41 | test_segment( |
42 | *point, |
43 | &LineSegment { |
44 | from: last, |
45 | to: first, |
46 | }, |
47 | &mut winding, |
48 | ); |
49 | } |
50 | PathEvent::Quadratic { from, ctrl, to } => { |
51 | let segment = QuadraticBezierSegment { from, ctrl, to }; |
52 | let (min, max) = segment.fast_bounding_range_y(); |
53 | if min > point.y || max < point.y { |
54 | continue; |
55 | } |
56 | segment.for_each_flattened(tolerance, &mut |line| { |
57 | test_segment(*point, line, &mut winding); |
58 | }); |
59 | } |
60 | PathEvent::Cubic { |
61 | from, |
62 | ctrl1, |
63 | ctrl2, |
64 | to, |
65 | } => { |
66 | let segment = CubicBezierSegment { |
67 | from, |
68 | ctrl1, |
69 | ctrl2, |
70 | to, |
71 | }; |
72 | let (min, max) = segment.fast_bounding_range_y(); |
73 | if min > point.y || max < point.y { |
74 | continue; |
75 | } |
76 | segment.for_each_flattened(tolerance, &mut |line| { |
77 | test_segment(*point, line, &mut winding); |
78 | }); |
79 | } |
80 | } |
81 | } |
82 | |
83 | winding |
84 | } |
85 | |
86 | fn test_segment( |
87 | point: Point, |
88 | segment: &LineSegment<f32>, |
89 | winding: &mut i32, |
90 | ) { |
91 | let y0 = segment.from.y; |
92 | let y1 = segment.to.y; |
93 | let min_y = f32::min(y0, y1); |
94 | let max_y = f32::max(y0, y1); |
95 | |
96 | if min_y > point.y |
97 | || max_y <= point.y |
98 | || f32::min(segment.from.x, segment.to.x) > point.x |
99 | { |
100 | return; |
101 | } |
102 | |
103 | if y0 == y1 { |
104 | return; |
105 | } |
106 | |
107 | let d = y1 - y0; |
108 | |
109 | |
110 | let t = (point.y - y0) / d; |
111 | let x = segment.sample(t).x; |
112 | |
113 | if x > point.x { |
114 | return; |
115 | } |
116 | |
117 | let w = if d > 0.0 { 1 } else { -1 }; |
118 | |
119 | *winding += w; |
120 | } |
121 | |
122 | #[test ] |
123 | fn test_hit_test() { |
124 | use crate::math::point; |
125 | use crate::path::Path; |
126 | |
127 | let mut builder = Path::builder(); |
128 | builder.begin(point(0.0, 0.0)); |
129 | builder.line_to(point(1.0, 0.0)); |
130 | builder.line_to(point(1.0, 1.0)); |
131 | builder.line_to(point(0.0, 1.0)); |
132 | builder.end(true); |
133 | builder.begin(point(0.25, 0.25)); |
134 | builder.line_to(point(0.75, 0.25)); |
135 | builder.line_to(point(0.75, 0.75)); |
136 | builder.line_to(point(0.20, 0.75)); |
137 | builder.end(true); |
138 | let path = builder.build(); |
139 | |
140 | assert!(!hit_test_path( |
141 | &point(-1.0, 0.5), |
142 | path.iter(), |
143 | FillRule::EvenOdd, |
144 | 0.1 |
145 | )); |
146 | assert!(!hit_test_path( |
147 | &point(2.0, 0.5), |
148 | path.iter(), |
149 | FillRule::EvenOdd, |
150 | 0.1 |
151 | )); |
152 | std::println!( |
153 | "winding {:?}" , |
154 | path_winding_number_at_position(&point(2.0, 0.0), path.iter(), 0.1) |
155 | ); |
156 | assert!(!hit_test_path( |
157 | &point(2.0, 0.0), |
158 | path.iter(), |
159 | FillRule::EvenOdd, |
160 | 0.1 |
161 | )); |
162 | assert!(!hit_test_path( |
163 | &point(0.5, -1.0), |
164 | path.iter(), |
165 | FillRule::EvenOdd, |
166 | 0.1 |
167 | )); |
168 | assert!(!hit_test_path( |
169 | &point(0.5, 2.0), |
170 | path.iter(), |
171 | FillRule::EvenOdd, |
172 | 0.1 |
173 | )); |
174 | |
175 | assert!(!hit_test_path( |
176 | &point(0.5, 0.5), |
177 | path.iter(), |
178 | FillRule::EvenOdd, |
179 | 0.1 |
180 | )); |
181 | assert!(hit_test_path( |
182 | &point(0.5, 0.5), |
183 | path.iter(), |
184 | FillRule::NonZero, |
185 | 0.1 |
186 | )); |
187 | assert!(hit_test_path( |
188 | &point(0.2, 0.5), |
189 | path.iter(), |
190 | FillRule::EvenOdd, |
191 | 0.1 |
192 | )); |
193 | assert!(hit_test_path( |
194 | &point(0.8, 0.5), |
195 | path.iter(), |
196 | FillRule::EvenOdd, |
197 | 0.1 |
198 | )); |
199 | } |
200 | |
201 | #[test ] |
202 | fn hit_test_point_aligned() { |
203 | use crate::math::point; |
204 | use crate::path::polygon::Polygon; |
205 | |
206 | let poly = Polygon { |
207 | points: &[ |
208 | point(-10.0, 10.0), |
209 | point(10.0, 10.0), |
210 | point(10.0, 5.0), |
211 | point(10.0, -10.0), |
212 | point(-10.0, -10.0), |
213 | ], |
214 | closed: true, |
215 | }; |
216 | |
217 | assert!(hit_test_path( |
218 | &point(0.0, 5.0), |
219 | poly.path_events(), |
220 | FillRule::NonZero, |
221 | 0.1 |
222 | )); |
223 | assert!(!hit_test_path( |
224 | &point(15.0, 5.0), |
225 | poly.path_events(), |
226 | FillRule::NonZero, |
227 | 0.1 |
228 | )); |
229 | } |
230 | |
231 | #[test ] |
232 | fn hit_test_double_square() { |
233 | use crate::math::point; |
234 | use crate::path::Path; |
235 | |
236 | let mut builder = Path::builder(); |
237 | builder.begin(point(0.0, 0.0)); |
238 | builder.line_to(point(1.0, 0.0)); |
239 | builder.line_to(point(1.0, 1.0)); |
240 | builder.line_to(point(0.0, 1.0)); |
241 | builder.line_to(point(0.0, 0.0)); |
242 | builder.line_to(point(1.0, 0.0)); |
243 | builder.line_to(point(1.0, 1.0)); |
244 | builder.line_to(point(0.0, 1.0)); |
245 | builder.end(true); |
246 | let path = builder.build(); |
247 | |
248 | assert_eq!( |
249 | path_winding_number_at_position(&point(0.5, 0.5), &path, 0.1), |
250 | -2 |
251 | ); |
252 | } |
253 | |
254 | #[test ] |
255 | fn hit_test_double_count() { |
256 | use crate::math::point; |
257 | use crate::path::Path; |
258 | |
259 | let mut builder = Path::builder(); |
260 | builder.begin(point(0.0, 0.0)); |
261 | builder.line_to(point(0.0, 1.0)); |
262 | builder.line_to(point(1.0, 1.0)); |
263 | builder.line_to(point(1.0, 2.0)); |
264 | builder.line_to(point(1.0, 3.0)); |
265 | builder.line_to(point(3.0, 3.0)); |
266 | builder.line_to(point(3.0, 0.0)); |
267 | builder.end(true); |
268 | let path = builder.build(); |
269 | |
270 | assert_eq!( |
271 | path_winding_number_at_position(&point(2.0, 1.0), &path, 0.1), |
272 | 1 |
273 | ); |
274 | assert_eq!( |
275 | path_winding_number_at_position(&point(2.0, 2.0), &path, 0.1), |
276 | 1 |
277 | ); |
278 | } |
279 | |
280 | #[test ] |
281 | fn issue_882() { |
282 | use crate::math::point; |
283 | use crate::path::Path; |
284 | let mut pb = Path::builder(); |
285 | pb.begin(point(0.0, 50.0)); |
286 | pb.line_to(point(50.0, 50.0)); |
287 | pb.line_to(point(50.0, 0.0)); |
288 | pb.line_to(point(100.0, 0.0)); |
289 | pb.line_to(point(100.0, 100.0)); |
290 | pb.line_to(point(0.0, 100.0)); |
291 | pb.line_to(point(0.0, 50.0)); |
292 | pb.end(true); |
293 | let p = pb.build(); |
294 | |
295 | let x = point(55.0, 50.0); |
296 | |
297 | assert!(hit_test_path(&x, p.iter(), FillRule::EvenOdd, 1.0)) |
298 | } |