1pub use u_geometry::robust::{
46 is_ccw, is_convex, orient2d, orient2d_filtered, orient2d_raw, point_in_triangle,
47 point_in_triangle_inclusive, Orientation,
48};
49
50#[inline]
59pub fn point_in_triangle_robust(
60 p: (f64, f64),
61 a: (f64, f64),
62 b: (f64, f64),
63 c: (f64, f64),
64) -> bool {
65 point_in_triangle(p, a, b, c)
66}
67
68#[inline]
73pub fn point_in_triangle_inclusive_robust(
74 p: (f64, f64),
75 a: (f64, f64),
76 b: (f64, f64),
77 c: (f64, f64),
78) -> bool {
79 point_in_triangle_inclusive(p, a, b, c)
80}
81
82#[inline]
86pub fn is_convex_robust(polygon: &[(f64, f64)]) -> bool {
87 is_convex(polygon)
88}
89
90#[inline]
94pub fn is_ccw_robust(polygon: &[(f64, f64)]) -> bool {
95 is_ccw(polygon)
96}
97
98pub fn signed_area_robust(polygon: &[(f64, f64)]) -> f64 {
118 let n = polygon.len();
119 if n < 3 {
120 return 0.0;
121 }
122
123 let mut sum = 0.0;
125 let mut c = 0.0; for i in 0..n {
128 let j = (i + 1) % n;
129 let term = polygon[i].0 * polygon[j].1 - polygon[j].0 * polygon[i].1;
130
131 let y = term - c;
132 let t = sum + y;
133 c = (t - sum) - y;
134 sum = t;
135 }
136
137 sum / 2.0
138}
139
140#[derive(Debug, Clone, Copy)]
146pub struct ScalingConfig {
147 pub scale: f64,
149 pub inv_scale: f64,
151}
152
153impl ScalingConfig {
154 pub fn new(precision: u32) -> Self {
170 let scale = 10.0_f64.powi(precision as i32);
171 Self {
172 scale,
173 inv_scale: 1.0 / scale,
174 }
175 }
176
177 #[inline]
179 pub fn scale_coord(&self, x: f64) -> f64 {
180 (x * self.scale).round()
181 }
182
183 #[inline]
185 pub fn scale_point(&self, p: (f64, f64)) -> (f64, f64) {
186 (self.scale_coord(p.0), self.scale_coord(p.1))
187 }
188
189 #[inline]
191 pub fn unscale_coord(&self, x: f64) -> f64 {
192 x * self.inv_scale
193 }
194
195 #[inline]
197 pub fn unscale_point(&self, p: (f64, f64)) -> (f64, f64) {
198 (self.unscale_coord(p.0), self.unscale_coord(p.1))
199 }
200
201 pub fn scale_polygon(&self, polygon: &[(f64, f64)]) -> Vec<(f64, f64)> {
203 polygon.iter().map(|&p| self.scale_point(p)).collect()
204 }
205
206 pub fn unscale_polygon(&self, polygon: &[(f64, f64)]) -> Vec<(f64, f64)> {
208 polygon.iter().map(|&p| self.unscale_point(p)).collect()
209 }
210}
211
212impl Default for ScalingConfig {
213 fn default() -> Self {
215 Self::new(6)
216 }
217}
218
219#[inline]
230pub fn snap_to_grid(point: (f64, f64), resolution: f64) -> (f64, f64) {
231 (
232 (point.0 / resolution).round() * resolution,
233 (point.1 / resolution).round() * resolution,
234 )
235}
236
237pub fn snap_polygon_to_grid(polygon: &[(f64, f64)], resolution: f64) -> Vec<(f64, f64)> {
239 polygon
240 .iter()
241 .map(|&p| snap_to_grid(p, resolution))
242 .collect()
243}
244
245#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_orient2d_basic() {
255 let a = (0.0, 0.0);
257 let b = (1.0, 0.0);
258 let c = (0.5, 1.0);
259
260 assert_eq!(orient2d(a, b, c), Orientation::CounterClockwise);
261 assert_eq!(orient2d(a, c, b), Orientation::Clockwise);
262 }
263
264 #[test]
265 fn test_orient2d_collinear() {
266 let a = (0.0, 0.0);
267 let b = (1.0, 1.0);
268 let c = (2.0, 2.0);
269
270 assert_eq!(orient2d(a, b, c), Orientation::Collinear);
271 }
272
273 #[test]
274 fn test_orient2d_near_collinear() {
275 let a = (0.0, 0.0);
276 let b = (1.0, 1.0);
277 let c = (2.0, 2.0 + 1e-15);
278
279 let result = orient2d(a, b, c);
280 assert!(
281 result == Orientation::Collinear || result == Orientation::CounterClockwise,
282 "Expected collinear or CCW, got {:?}",
283 result
284 );
285 }
286
287 #[test]
288 fn test_orient2d_filtered_fast_path() {
289 let a = (0.0, 0.0);
290 let b = (10.0, 0.0);
291 let c = (5.0, 10.0);
292
293 assert_eq!(orient2d_filtered(a, b, c), Orientation::CounterClockwise);
294 }
295
296 #[test]
297 fn test_point_in_triangle_robust() {
298 let a = (0.0, 0.0);
299 let b = (10.0, 0.0);
300 let c = (5.0, 10.0);
301
302 assert!(point_in_triangle_robust((5.0, 3.0), a, b, c));
303 assert!(!point_in_triangle_robust((20.0, 5.0), a, b, c));
304 assert!(!point_in_triangle_robust((5.0, 0.0), a, b, c));
305 }
306
307 #[test]
308 fn test_point_in_triangle_inclusive() {
309 let a = (0.0, 0.0);
310 let b = (10.0, 0.0);
311 let c = (5.0, 10.0);
312
313 assert!(point_in_triangle_inclusive_robust((5.0, 3.0), a, b, c));
314 assert!(point_in_triangle_inclusive_robust((5.0, 0.0), a, b, c));
315 assert!(point_in_triangle_inclusive_robust((0.0, 0.0), a, b, c));
316 assert!(!point_in_triangle_inclusive_robust((20.0, 5.0), a, b, c));
317 }
318
319 #[test]
320 fn test_is_convex_robust() {
321 let square = vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
322 assert!(is_convex_robust(&square));
323
324 let triangle = vec![(0.0, 0.0), (10.0, 0.0), (5.0, 10.0)];
325 assert!(is_convex_robust(&triangle));
326
327 let l_shape = vec![
328 (0.0, 0.0),
329 (10.0, 0.0),
330 (10.0, 5.0),
331 (5.0, 5.0),
332 (5.0, 10.0),
333 (0.0, 10.0),
334 ];
335 assert!(!is_convex_robust(&l_shape));
336 }
337
338 #[test]
339 fn test_is_ccw_robust() {
340 let ccw_square = vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
341 assert!(is_ccw_robust(&ccw_square));
342
343 let cw_square = vec![(0.0, 0.0), (0.0, 10.0), (10.0, 10.0), (10.0, 0.0)];
344 assert!(!is_ccw_robust(&cw_square));
345 }
346
347 #[test]
348 fn test_signed_area_robust() {
349 let ccw_square = vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
350 let area = signed_area_robust(&ccw_square);
351 assert!((area - 100.0).abs() < 1e-10);
352
353 let cw_square = vec![(0.0, 0.0), (0.0, 10.0), (10.0, 10.0), (10.0, 0.0)];
354 let area = signed_area_robust(&cw_square);
355 assert!((area + 100.0).abs() < 1e-10);
356 }
357
358 #[test]
359 fn test_scaling_config() {
360 let config = ScalingConfig::new(3);
361
362 let p = (1.234, 5.678);
363 let scaled = config.scale_point(p);
364 assert_eq!(scaled, (1234.0, 5678.0));
365
366 let unscaled = config.unscale_point(scaled);
367 assert!((unscaled.0 - p.0).abs() < 1e-10);
368 assert!((unscaled.1 - p.1).abs() < 1e-10);
369 }
370
371 #[test]
372 fn test_snap_to_grid() {
373 let p = (1.23, 4.56);
374
375 let snapped = snap_to_grid(p, 0.5);
376 assert_eq!(snapped, (1.0, 4.5));
377
378 let snapped = snap_to_grid(p, 1.0);
379 assert_eq!(snapped, (1.0, 5.0));
380 }
381
382 #[test]
383 fn test_orientation_methods() {
384 assert!(Orientation::CounterClockwise.is_ccw());
385 assert!(!Orientation::CounterClockwise.is_cw());
386 assert!(!Orientation::CounterClockwise.is_collinear());
387
388 assert!(!Orientation::Clockwise.is_ccw());
389 assert!(Orientation::Clockwise.is_cw());
390 assert!(!Orientation::Clockwise.is_collinear());
391
392 assert!(!Orientation::Collinear.is_ccw());
393 assert!(!Orientation::Collinear.is_cw());
394 assert!(Orientation::Collinear.is_collinear());
395 }
396
397 #[test]
398 fn test_degenerate_triangle() {
399 let a = (0.0, 0.0);
400 let b = (5.0, 0.0);
401 let c = (10.0, 0.0);
402
403 assert!(!point_in_triangle_robust((5.0, 0.0), a, b, c));
404 }
405
406 #[test]
407 fn test_extreme_coordinates() {
408 let a = (1e10, 1e10);
409 let b = (1e10 + 1.0, 1e10);
410 let c = (1e10 + 0.5, 1e10 + 1.0);
411
412 assert_eq!(orient2d(a, b, c), Orientation::CounterClockwise);
413
414 let a = (1e-10, 1e-10);
415 let b = (1e-10 + 1e-12, 1e-10);
416 let c = (1e-10 + 5e-13, 1e-10 + 1e-12);
417
418 let result = orient2d(a, b, c);
419 assert!(
420 result == Orientation::CounterClockwise || result == Orientation::Collinear,
421 "Unexpected orientation: {:?}",
422 result
423 );
424 }
425}