Skip to main content

clipper2_rust/
core.rs

1//! Core types and structures for Clipper2
2//!
3//! Direct port from clipper.core.h
4//! This module contains the fundamental data types and basic operations
5
6use num_traits::{Float, Num, Zero};
7use std::fmt::{Debug, Display};
8
9/// Fill rule determines how polygons with self-intersections are filled
10/// Direct port from clipper.core.h line 108
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12#[repr(C)]
13pub enum FillRule {
14    /// Even-odd fill rule (also known as Alternate)
15    #[default]
16    EvenOdd,
17    /// Non-zero fill rule (also known as Winding)
18    NonZero,
19    /// Positive fill rule
20    Positive,
21    /// Negative fill rule
22    Negative,
23}
24
25/// Location of a point relative to a rectangle
26/// Direct port from clipper.rectclip.h line 20
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[repr(C)]
29pub enum Location {
30    Left,
31    Top,
32    Right,
33    Bottom,
34    Inside,
35}
36
37/// Exception type for Clipper2 errors
38/// Direct port from clipper.core.h line 27
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Clipper2Exception {
41    description: String,
42}
43
44impl Clipper2Exception {
45    pub fn new(description: &str) -> Self {
46        Self {
47            description: description.to_string(),
48        }
49    }
50
51    pub fn description(&self) -> &str {
52        &self.description
53    }
54}
55
56impl Display for Clipper2Exception {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(f, "Clipper2Exception: {}", self.description)
59    }
60}
61
62impl std::error::Error for Clipper2Exception {}
63
64/// Handle errors by throwing appropriate exceptions
65/// Direct port from clipper.core.h line 73
66pub fn do_error(error_code: i32) -> Result<(), Clipper2Exception> {
67    use errors::*;
68
69    match error_code {
70        PRECISION_ERROR_I => Err(Clipper2Exception::new(PRECISION_ERROR)),
71        SCALE_ERROR_I => Err(Clipper2Exception::new(SCALE_ERROR)),
72        NON_PAIR_ERROR_I => Err(Clipper2Exception::new(NON_PAIR_ERROR)),
73        UNDEFINED_ERROR_I => Err(Clipper2Exception::new(UNDEFINED_ERROR)),
74        RANGE_ERROR_I => Err(Clipper2Exception::new(RANGE_ERROR)),
75        _ => Err(Clipper2Exception::new("Unknown error")),
76    }
77}
78
79/// Constants matching C++ implementation
80/// Direct port from clipper.core.h line 55-71
81pub mod constants {
82    /// PI constant
83    pub const PI: f64 = std::f64::consts::PI;
84
85    /// Maximum decimal precision for clipper operations
86    pub const CLIPPER2_MAX_DEC_PRECISION: i32 = 8;
87
88    /// Maximum coordinate value (INT64_MAX >> 2)
89    pub const MAX_COORD: i64 = i64::MAX >> 2;
90    /// Minimum coordinate value  
91    pub const MIN_COORD: i64 = -MAX_COORD;
92    /// Invalid coordinate sentinel
93    pub const INVALID: i64 = i64::MAX;
94    /// Maximum coordinate as double
95    pub const MAX_COORD_D: f64 = MAX_COORD as f64;
96    /// Minimum coordinate as double
97    pub const MIN_COORD_D: f64 = MIN_COORD as f64;
98    /// Maximum double value
99    pub const MAX_DBL: f64 = f64::MAX;
100}
101
102/// Error constants matching C++ implementation
103pub mod errors {
104    /// Precision exceeds the permitted range
105    pub const PRECISION_ERROR: &str = "Precision exceeds the permitted range";
106    /// Values exceed permitted range
107    pub const RANGE_ERROR: &str = "Values exceed permitted range";
108    /// Invalid scale (either 0 or too large)
109    pub const SCALE_ERROR: &str = "Invalid scale (either 0 or too large)";
110    /// There must be 2 values for each coordinate
111    pub const NON_PAIR_ERROR: &str = "There must be 2 values for each coordinate";
112    /// There is an undefined error in Clipper2
113    pub const UNDEFINED_ERROR: &str = "There is an undefined error in Clipper2";
114
115    /// Error codes (2^n) - non-fatal
116    pub const PRECISION_ERROR_I: i32 = 1;
117    /// Error codes (2^n) - non-fatal  
118    pub const SCALE_ERROR_I: i32 = 2;
119    /// Error codes (2^n) - non-fatal
120    pub const NON_PAIR_ERROR_I: i32 = 4;
121    /// Error codes (2^n) - fatal
122    pub const UNDEFINED_ERROR_I: i32 = 32;
123    /// Error codes (2^n)
124    pub const RANGE_ERROR_I: i32 = 64;
125}
126
127/// 2D point with generic numeric type
128/// Direct port from clipper.core.h line 117
129#[derive(Debug, Clone, Copy, PartialEq, Default)]
130#[repr(C)]
131pub struct Point<T> {
132    pub x: T,
133    pub y: T,
134}
135
136impl<T> Point<T>
137where
138    T: Num + Copy,
139{
140    /// Create a new point
141    pub fn new(x: T, y: T) -> Self {
142        Self { x, y }
143    }
144
145    /// Create a zero point
146    pub fn zero() -> Self
147    where
148        T: Zero,
149    {
150        Self {
151            x: T::zero(),
152            y: T::zero(),
153        }
154    }
155}
156
157impl<T> Point<T>
158where
159    T: Num + Copy,
160{
161    /// Add two points
162    pub fn add_point(self, other: Self) -> Self {
163        Self {
164            x: self.x + other.x,
165            y: self.y + other.y,
166        }
167    }
168
169    /// Subtract two points
170    pub fn sub_point(self, other: Self) -> Self {
171        Self {
172            x: self.x - other.x,
173            y: self.y - other.y,
174        }
175    }
176
177    /// Negate a point
178    pub fn negate(self) -> Self {
179        Self {
180            x: T::zero() - self.x,
181            y: T::zero() - self.y,
182        }
183    }
184}
185
186impl<T> Point<T>
187where
188    T: Num + Copy + PartialOrd,
189{
190    /// Scale a point by a floating-point factor  
191    pub fn scale<F>(self, scale: F) -> Point<F>
192    where
193        F: Float,
194        T: Into<F>,
195    {
196        Point {
197            x: self.x.into() * scale,
198            y: self.y.into() * scale,
199        }
200    }
201}
202
203// Operator overloads matching C++
204impl<T> std::ops::Add for Point<T>
205where
206    T: Num + Copy,
207{
208    type Output = Self;
209
210    fn add(self, rhs: Self) -> Self::Output {
211        self.add_point(rhs)
212    }
213}
214
215impl<T> std::ops::Sub for Point<T>
216where
217    T: Num + Copy,
218{
219    type Output = Self;
220
221    fn sub(self, rhs: Self) -> Self::Output {
222        self.sub_point(rhs)
223    }
224}
225
226impl<T> std::ops::Neg for Point<T>
227where
228    T: Num + Copy,
229{
230    type Output = Self;
231
232    fn neg(self) -> Self::Output {
233        self.negate()
234    }
235}
236
237/// Rectangle with generic numeric type
238/// Direct port from clipper.core.h line 295
239#[derive(Debug, Clone, Copy, Default)]
240#[repr(C)]
241pub struct Rect<T> {
242    pub left: T,
243    pub top: T,
244    pub right: T,
245    pub bottom: T,
246}
247
248impl<T> Rect<T>
249where
250    T: Num + Copy + PartialOrd,
251{
252    /// Create a new rectangle
253    pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
254        Self {
255            left,
256            top,
257            right,
258            bottom,
259        }
260    }
261
262    /// Create a rectangle, valid by default or invalid if specified
263    /// Direct port from clipper.core.h line 307
264    pub fn new_with_validity(is_valid: bool) -> Self
265    where
266        T: num_traits::Bounded,
267    {
268        if is_valid {
269            Self {
270                left: T::zero(),
271                top: T::zero(),
272                right: T::zero(),
273                bottom: T::zero(),
274            }
275        } else {
276            Self {
277                left: T::max_value(),
278                top: T::max_value(),
279                right: T::min_value(),
280                bottom: T::min_value(),
281            }
282        }
283    }
284
285    /// Create an invalid rectangle
286    /// Direct port from clipper.core.h line 320
287    pub fn invalid() -> Self
288    where
289        T: num_traits::Bounded,
290    {
291        Self {
292            left: T::max_value(),
293            top: T::max_value(),
294            right: T::min_value(),
295            bottom: T::min_value(),
296        }
297    }
298
299    /// Get midpoint of rectangle
300    /// Direct port from clipper.core.h line 336
301    pub fn mid_point(&self) -> Point<T> {
302        Point {
303            x: (self.left + self.right) / (T::one() + T::one()),
304            y: (self.top + self.bottom) / (T::one() + T::one()),
305        }
306    }
307
308    /// Convert rectangle to path (clockwise from top-left)
309    /// Direct port from clipper.core.h line 341
310    pub fn as_path(&self) -> Path<T> {
311        vec![
312            Point::new(self.left, self.top),
313            Point::new(self.right, self.top),
314            Point::new(self.right, self.bottom),
315            Point::new(self.left, self.bottom),
316        ]
317    }
318
319    /// Check if point is contained within rectangle (exclusive bounds)
320    /// Direct port from clipper.core.h line 352
321    pub fn contains_point(&self, pt: &Point<T>) -> bool {
322        pt.x > self.left && pt.x < self.right && pt.y > self.top && pt.y < self.bottom
323    }
324
325    /// Check if another rectangle is fully contained within this rectangle
326    /// Direct port from clipper.core.h line 357
327    pub fn contains_rect(&self, rec: &Rect<T>) -> bool {
328        rec.left >= self.left
329            && rec.right <= self.right
330            && rec.top >= self.top
331            && rec.bottom <= self.bottom
332    }
333
334    /// Check if this rectangle intersects with another
335    /// Direct port from clipper.core.h line 372
336    pub fn intersects(&self, rec: &Rect<T>) -> bool {
337        let max_left = if self.left > rec.left {
338            self.left
339        } else {
340            rec.left
341        };
342        let min_right = if self.right < rec.right {
343            self.right
344        } else {
345            rec.right
346        };
347        let max_top = if self.top > rec.top {
348            self.top
349        } else {
350            rec.top
351        };
352        let min_bottom = if self.bottom < rec.bottom {
353            self.bottom
354        } else {
355            rec.bottom
356        };
357
358        max_left <= min_right && max_top <= min_bottom
359    }
360
361    /// Check if rectangle is valid (not using max sentinel values)
362    /// Direct port from clipper.core.h line 329
363    pub fn is_valid(&self) -> bool
364    where
365        T: num_traits::Bounded + PartialEq,
366    {
367        self.left != T::max_value()
368    }
369
370    /// Get width of rectangle
371    pub fn width(&self) -> T {
372        self.right - self.left
373    }
374
375    /// Get height of rectangle  
376    pub fn height(&self) -> T {
377        self.bottom - self.top
378    }
379
380    /// Set width, adjusting right edge
381    pub fn set_width(&mut self, width: T) {
382        self.right = self.left + width;
383    }
384
385    /// Set height, adjusting bottom edge
386    pub fn set_height(&mut self, height: T) {
387        self.bottom = self.top + height;
388    }
389
390    /// Check if rectangle is empty
391    pub fn is_empty(&self) -> bool {
392        self.left >= self.right || self.top >= self.bottom
393    }
394}
395
396impl<T> Rect<T>
397where
398    T: Float + Copy,
399{
400    /// Scale rectangle by floating-point factor
401    pub fn scale(&mut self, scale: T) {
402        self.left = self.left * scale;
403        self.top = self.top * scale;
404        self.right = self.right * scale;
405        self.bottom = self.bottom * scale;
406    }
407}
408
409// Implement PartialEq for Rect to match C++ operator==
410// Direct port from clipper.core.h line 378
411impl<T> PartialEq for Rect<T>
412where
413    T: PartialEq,
414{
415    fn eq(&self, other: &Self) -> bool {
416        self.left == other.left
417            && self.right == other.right
418            && self.top == other.top
419            && self.bottom == other.bottom
420    }
421}
422
423// Implement += operator for Rect (union operation)
424// Direct port from clipper.core.h line 383
425impl<T> std::ops::AddAssign for Rect<T>
426where
427    T: Num + Copy + PartialOrd,
428{
429    fn add_assign(&mut self, other: Self) {
430        self.left = if self.left < other.left {
431            self.left
432        } else {
433            other.left
434        };
435        self.top = if self.top < other.top {
436            self.top
437        } else {
438            other.top
439        };
440        self.right = if self.right > other.right {
441            self.right
442        } else {
443            other.right
444        };
445        self.bottom = if self.bottom > other.bottom {
446            self.bottom
447        } else {
448            other.bottom
449        };
450    }
451}
452
453// Implement + operator for Rect (union operation)
454// Direct port from clipper.core.h line 392
455impl<T> std::ops::Add for Rect<T>
456where
457    T: Num + Copy + PartialOrd,
458{
459    type Output = Self;
460
461    fn add(self, other: Self) -> Self {
462        let mut result = self;
463        result += other;
464        result
465    }
466}
467
468// Type aliases matching C++ implementation
469pub type Point64 = Point<i64>;
470pub type PointD = Point<f64>;
471pub type Rect64 = Rect<i64>;
472pub type RectD = Rect<f64>;
473
474/// Vector of points forming a path
475pub type Path<T> = Vec<Point<T>>;
476pub type Path64 = Path<i64>;
477pub type PathD = Path<f64>;
478
479/// Vector of paths
480pub type Paths<T> = Vec<Path<T>>;
481pub type Paths64 = Paths<i64>;
482pub type PathsD = Paths<f64>;
483
484/// Invalid point constants
485pub const INVALID_POINT64: Point64 = Point64 {
486    x: i64::MAX,
487    y: i64::MAX,
488};
489
490pub const INVALID_POINTD: PointD = PointD {
491    x: f64::MAX,
492    y: f64::MAX,
493};
494
495/// Calculate midpoint between two points
496/// Direct port from clipper.core.h line 278
497#[inline]
498pub fn mid_point<T>(p1: Point<T>, p2: Point<T>) -> Point<T>
499where
500    T: Num + Copy,
501{
502    Point {
503        x: (p1.x + p2.x) / (T::one() + T::one()),
504        y: (p1.y + p2.y) / (T::one() + T::one()),
505    }
506}
507
508/// Helper trait for converting to f64 - matching C++ static_cast<double> behavior
509pub trait ToF64 {
510    fn to_f64(self) -> f64;
511}
512
513impl ToF64 for i64 {
514    fn to_f64(self) -> f64 {
515        self as f64
516    }
517}
518
519impl ToF64 for i32 {
520    fn to_f64(self) -> f64 {
521        self as f64
522    }
523}
524
525impl ToF64 for f64 {
526    fn to_f64(self) -> f64 {
527        self
528    }
529}
530
531impl ToF64 for f32 {
532    fn to_f64(self) -> f64 {
533        self as f64
534    }
535}
536
537/// Calculate cross product of two vectors formed by three points  
538/// Direct port from clipper.core.h line 810
539#[inline]
540pub fn cross_product_three_points<T>(pt1: Point<T>, pt2: Point<T>, pt3: Point<T>) -> f64
541where
542    T: Copy + ToF64,
543{
544    let pt1_x = pt1.x.to_f64();
545    let pt1_y = pt1.y.to_f64();
546    let pt2_x = pt2.x.to_f64();
547    let pt2_y = pt2.y.to_f64();
548    let pt3_x = pt3.x.to_f64();
549    let pt3_y = pt3.y.to_f64();
550
551    (pt2_x - pt1_x) * (pt3_y - pt2_y) - (pt2_y - pt1_y) * (pt3_x - pt2_x)
552}
553
554/// Calculate cross product of two vectors
555/// Direct port from clipper.core.h line 816
556#[inline]
557pub fn cross_product_two_vectors<T>(vec1: Point<T>, vec2: Point<T>) -> f64
558where
559    T: Copy + ToF64,
560{
561    let vec1_x = vec1.x.to_f64();
562    let vec1_y = vec1.y.to_f64();
563    let vec2_x = vec2.x.to_f64();
564    let vec2_y = vec2.y.to_f64();
565
566    vec1_y * vec2_x - vec2_y * vec1_x
567}
568
569/// Calculate dot product of two vectors formed by three points
570/// Direct port from clipper.core.h line 822
571#[inline]
572pub fn dot_product_three_points<T>(pt1: Point<T>, pt2: Point<T>, pt3: Point<T>) -> f64
573where
574    T: Copy + ToF64,
575{
576    let pt1_x = pt1.x.to_f64();
577    let pt1_y = pt1.y.to_f64();
578    let pt2_x = pt2.x.to_f64();
579    let pt2_y = pt2.y.to_f64();
580    let pt3_x = pt3.x.to_f64();
581    let pt3_y = pt3.y.to_f64();
582
583    (pt2_x - pt1_x) * (pt3_x - pt2_x) + (pt2_y - pt1_y) * (pt3_y - pt2_y)
584}
585
586/// Calculate dot product of two vectors
587/// Direct port from clipper.core.h line 828
588#[inline]
589pub fn dot_product_two_vectors<T>(vec1: Point<T>, vec2: Point<T>) -> f64
590where
591    T: Copy + ToF64,
592{
593    let vec1_x = vec1.x.to_f64();
594    let vec1_y = vec1.y.to_f64();
595    let vec2_x = vec2.x.to_f64();
596    let vec2_y = vec2.y.to_f64();
597
598    vec1_x * vec2_x + vec1_y * vec2_y
599}
600
601/// Helper for returning -1, 0, or 1 based on sign
602/// Direct port from clipper.core.h line 697  
603#[inline]
604pub fn tri_sign(x: i64) -> i32 {
605    if x > 0 {
606        1
607    } else if x < 0 {
608        -1
609    } else {
610        0
611    }
612}
613
614/// 128-bit unsigned integer struct for high-precision multiplication
615/// Direct port from clipper.core.h line 685
616#[derive(Debug, Clone, Copy, PartialEq)]
617pub struct UInt128Struct {
618    pub lo: u64,
619    pub hi: u64,
620}
621
622/// Multiply two 64-bit unsigned integers to get 128-bit result
623/// Direct port from clipper.core.h line 690
624#[inline]
625pub fn multiply_u64(a: u64, b: u64) -> UInt128Struct {
626    // Lambdas from C++: lo = x & 0xFFFFFFFF, hi = x >> 32
627    let lo = |x: u64| -> u64 { x & 0xFFFFFFFF };
628    let hi = |x: u64| -> u64 { x >> 32 };
629
630    let x1 = lo(a) * lo(b);
631    let x2 = hi(a) * lo(b) + hi(x1);
632    let x3 = lo(a) * hi(b) + lo(x2);
633    let lobits = lo(x3) << 32 | lo(x1);
634    let hibits = hi(a) * hi(b) + hi(x2) + hi(x3);
635
636    UInt128Struct {
637        lo: lobits,
638        hi: hibits,
639    }
640}
641
642/// Check if products a*b and c*d are equal using exact 128-bit arithmetic
643/// Direct port from clipper.core.h line 703
644#[inline]
645pub fn products_are_equal(a: i64, b: i64, c: i64, d: i64) -> bool {
646    // For 128-bit capable systems, use i128 for simplicity
647    #[cfg(target_pointer_width = "64")]
648    {
649        let ab = a as i128 * b as i128;
650        let cd = c as i128 * d as i128;
651        ab == cd
652    }
653
654    // For other systems or if we want exact C++ behavior, use the manual implementation
655    #[cfg(not(target_pointer_width = "64"))]
656    {
657        // Convert to unsigned for overflow calculations
658        let abs_a = a.unsigned_abs();
659        let abs_b = b.unsigned_abs();
660        let abs_c = c.unsigned_abs();
661        let abs_d = d.unsigned_abs();
662
663        let ab = multiply_u64(abs_a, abs_b);
664        let cd = multiply_u64(abs_c, abs_d);
665
666        // Calculate signs - important to differentiate 0 values
667        let sign_ab = tri_sign(a) * tri_sign(b);
668        let sign_cd = tri_sign(c) * tri_sign(d);
669
670        ab == cd && sign_ab == sign_cd
671    }
672}
673
674/// Strip duplicate consecutive points from a path
675/// Direct port from clipper.core.h line 658
676#[inline]
677pub fn strip_duplicates_path<T>(path: &mut Path<T>, is_closed_path: bool)
678where
679    T: PartialEq + Clone,
680{
681    // Use stable dedup to remove consecutive duplicates
682    path.dedup();
683
684    // For closed paths, also remove duplicates between last and first points
685    if is_closed_path {
686        while path.len() > 1 && path.last() == path.first() {
687            path.pop();
688        }
689    }
690}
691
692/// Strip duplicate consecutive points from multiple paths
693/// Direct port from clipper.core.h line 670
694#[inline]
695pub fn strip_duplicates_paths<T>(paths: &mut Paths<T>, is_closed_path: bool)
696where
697    T: PartialEq + Clone,
698{
699    for path in paths.iter_mut() {
700        strip_duplicates_path(path, is_closed_path);
701    }
702}
703
704/// Check if precision is within acceptable range and adjust if needed
705/// Direct port from clipper.core.h line 682
706#[inline]
707pub fn check_precision_range(precision: &mut i32, error_code: &mut i32) {
708    use constants::CLIPPER2_MAX_DEC_PRECISION;
709    use errors::PRECISION_ERROR_I;
710
711    if *precision >= -CLIPPER2_MAX_DEC_PRECISION && *precision <= CLIPPER2_MAX_DEC_PRECISION {
712        return;
713    }
714
715    *error_code |= PRECISION_ERROR_I; // non-fatal error
716
717    // In Rust, we return the error instead of calling DoError with exceptions
718    // This matches the C++ behavior when exceptions are disabled
719
720    *precision = if *precision > 0 {
721        CLIPPER2_MAX_DEC_PRECISION
722    } else {
723        -CLIPPER2_MAX_DEC_PRECISION
724    };
725}
726
727/// Check precision range without error code (convenience function)
728/// Direct port from clipper.core.h line 691
729#[inline]
730pub fn check_precision_range_simple(precision: &mut i32) {
731    let mut error_code = 0;
732    check_precision_range(precision, &mut error_code);
733}
734
735/// Calculate the bounding rectangle of a path
736/// Direct port from clipper.core.h line 432
737#[inline]
738pub fn get_bounds_path<T>(path: &Path<T>) -> Rect<T>
739where
740    T: Copy + PartialOrd + num_traits::Bounded + num_traits::Num,
741{
742    let mut xmin = T::max_value();
743    let mut ymin = T::max_value();
744    let mut xmax = T::min_value();
745    let mut ymax = T::min_value();
746
747    for p in path {
748        if p.x < xmin {
749            xmin = p.x;
750        }
751        if p.x > xmax {
752            xmax = p.x;
753        }
754        if p.y < ymin {
755            ymin = p.y;
756        }
757        if p.y > ymax {
758            ymax = p.y;
759        }
760    }
761
762    Rect::new(xmin, ymin, xmax, ymax)
763}
764
765/// Calculate the bounding rectangle of multiple paths
766/// Direct port from clipper.core.h line 449
767#[inline]
768pub fn get_bounds_paths<T>(paths: &Paths<T>) -> Rect<T>
769where
770    T: Copy + PartialOrd + num_traits::Bounded + num_traits::Num,
771{
772    let mut xmin = T::max_value();
773    let mut ymin = T::max_value();
774    let mut xmax = T::min_value();
775    let mut ymax = T::min_value();
776
777    for path in paths {
778        for p in path {
779            if p.x < xmin {
780                xmin = p.x;
781            }
782            if p.x > xmax {
783                xmax = p.x;
784            }
785            if p.y < ymin {
786                ymin = p.y;
787            }
788            if p.y > ymax {
789                ymax = p.y;
790            }
791        }
792    }
793
794    Rect::new(xmin, ymin, xmax, ymax)
795}
796
797/// Calculate the bounding rectangle of a path with type conversion
798/// Direct port from clipper.core.h line 467
799#[inline]
800pub fn get_bounds_path_convert<T, T2>(path: &Path<T2>) -> Rect<T>
801where
802    T: Copy + PartialOrd + num_traits::Bounded + num_traits::Num,
803    T2: Copy + Into<T>,
804{
805    let mut xmin = T::max_value();
806    let mut ymin = T::max_value();
807    let mut xmax = T::min_value();
808    let mut ymax = T::min_value();
809
810    for p in path {
811        let x: T = p.x.into();
812        let y: T = p.y.into();
813        if x < xmin {
814            xmin = x;
815        }
816        if x > xmax {
817            xmax = x;
818        }
819        if y < ymin {
820            ymin = y;
821        }
822        if y > ymax {
823            ymax = y;
824        }
825    }
826
827    Rect::new(xmin, ymin, xmax, ymax)
828}
829
830/// Calculate the bounding rectangle of multiple paths with type conversion
831/// Direct port from clipper.core.h line 484
832#[inline]
833pub fn get_bounds_paths_convert<T, T2>(paths: &Paths<T2>) -> Rect<T>
834where
835    T: Copy + PartialOrd + num_traits::Bounded + num_traits::Num,
836    T2: Copy + Into<T>,
837{
838    let mut xmin = T::max_value();
839    let mut ymin = T::max_value();
840    let mut xmax = T::min_value();
841    let mut ymax = T::min_value();
842
843    for path in paths {
844        for p in path {
845            let x: T = p.x.into();
846            let y: T = p.y.into();
847            if x < xmin {
848                xmin = x;
849            }
850            if x > xmax {
851                xmax = x;
852            }
853            if y < ymin {
854                ymin = y;
855            }
856            if y > ymax {
857                ymax = y;
858            }
859        }
860    }
861
862    Rect::new(xmin, ymin, xmax, ymax)
863}
864
865/// Square a value (matches C++ template<typename T> inline double Sqr(T val))
866/// Direct port from clipper.core.h line 611
867#[inline]
868pub fn sqr<T>(val: T) -> f64
869where
870    T: ToF64,
871{
872    let d = val.to_f64();
873    d * d
874}
875
876/// Calculate squared distance between two points
877/// Direct port from clipper.core.h line 834
878#[inline]
879pub fn distance_sqr<T>(pt1: Point<T>, pt2: Point<T>) -> f64
880where
881    T: Copy + ToF64,
882{
883    sqr(pt1.x.to_f64() - pt2.x.to_f64()) + sqr(pt1.y.to_f64() - pt2.y.to_f64())
884}
885
886/// Calculate squared perpendicular distance from point to line
887/// Direct port from clipper.core.h line 840
888#[inline]
889pub fn perpendicular_distance_from_line_sqr<T>(
890    pt: Point<T>,
891    line1: Point<T>,
892    line2: Point<T>,
893) -> f64
894where
895    T: Copy + ToF64,
896{
897    let a = pt.x.to_f64() - line1.x.to_f64();
898    let b = pt.y.to_f64() - line1.y.to_f64();
899    let c = line2.x.to_f64() - line1.x.to_f64();
900    let d = line2.y.to_f64() - line1.y.to_f64();
901
902    if c == 0.0 && d == 0.0 {
903        return 0.0;
904    }
905
906    sqr(a * d - c * b) / (c * c + d * d)
907}
908
909/// Calculate area of a polygon path using the shoelace formula
910/// Direct port from clipper.core.h line 854
911#[inline]
912pub fn area<T>(path: &Path<T>) -> f64
913where
914    T: Copy + ToF64,
915{
916    let cnt = path.len();
917    if cnt < 3 {
918        return 0.0;
919    }
920
921    // Use the standard shoelace formula for now - matches what C++ should produce
922    let mut area = 0.0;
923    for i in 0..cnt {
924        let j = (i + 1) % cnt;
925        let xi = path[i].x.to_f64();
926        let yi = path[i].y.to_f64();
927        let xj = path[j].x.to_f64();
928        let yj = path[j].y.to_f64();
929
930        area += xi * yj - xj * yi;
931    }
932
933    area * 0.5
934}
935
936/// Calculate total area of multiple polygon paths
937/// Direct port from clipper.core.h line 874
938#[inline]
939pub fn area_paths<T>(paths: &Paths<T>) -> f64
940where
941    T: Copy + ToF64,
942{
943    let mut total_area = 0.0;
944    for path in paths {
945        total_area += area(path);
946    }
947    total_area
948}
949
950/// Test if a polygon has positive orientation (counterclockwise)
951/// Direct port from clipper.core.h line 886
952#[inline]
953pub fn is_positive<T>(poly: &Path<T>) -> bool
954where
955    T: Copy + ToF64,
956{
957    area(poly) >= 0.0
958}
959
960/// Get the location of a point relative to a rectangle
961/// Returns false if the point is on the rectangle edge, true otherwise
962/// Direct port from clipper.rectclip.cpp line 37
963#[inline]
964pub fn get_location(rec: &Rect64, pt: &Point64, loc: &mut Location) -> bool {
965    if pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom {
966        *loc = Location::Left;
967        return false;
968    } else if pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom {
969        *loc = Location::Right;
970        return false;
971    } else if pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right {
972        *loc = Location::Top;
973        return false;
974    } else if pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right {
975        *loc = Location::Bottom;
976        return false;
977    } else if pt.x < rec.left {
978        *loc = Location::Left;
979    } else if pt.x > rec.right {
980        *loc = Location::Right;
981    } else if pt.y < rec.top {
982        *loc = Location::Top;
983    } else if pt.y > rec.bottom {
984        *loc = Location::Bottom;
985    } else {
986        *loc = Location::Inside;
987    }
988    true
989}
990
991/// Check if a line segment between two points is horizontal
992/// Returns true if both points have the same y-coordinate
993/// Direct port from clipper.rectclip.cpp line 68
994#[inline]
995pub fn is_horizontal(pt1: &Point64, pt2: &Point64) -> bool {
996    pt1.y == pt2.y
997}
998
999/// Result of point-in-polygon test
1000/// Direct port from clipper.core.h line 1046
1001#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1002#[repr(C)]
1003pub enum PointInPolygonResult {
1004    IsOn,
1005    IsInside,
1006    IsOutside,
1007}
1008
1009/// Calculate exact intersection point between two line segments for Point64
1010/// Direct port from clipper.core.h line 902 - simplified version for i64 coordinates
1011#[inline]
1012pub fn get_segment_intersect_pt(
1013    ln1a: Point64,
1014    ln1b: Point64,
1015    ln2a: Point64,
1016    ln2b: Point64,
1017    ip: &mut Point64,
1018) -> bool {
1019    // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
1020    let dx1 = ln1b.x as f64 - ln1a.x as f64;
1021    let dy1 = ln1b.y as f64 - ln1a.y as f64;
1022    let dx2 = ln2b.x as f64 - ln2a.x as f64;
1023    let dy2 = ln2b.y as f64 - ln2a.y as f64;
1024
1025    let det = dy1 * dx2 - dy2 * dx1;
1026    if det == 0.0 {
1027        return false;
1028    }
1029
1030    let t = ((ln1a.x as f64 - ln2a.x as f64) * dy2 - (ln1a.y as f64 - ln2a.y as f64) * dx2) / det;
1031
1032    if t <= 0.0 {
1033        *ip = ln1a;
1034    } else if t >= 1.0 {
1035        *ip = ln1b;
1036    } else {
1037        // C++ uses static_cast<T> which truncates toward zero, not rounds
1038        ip.x = (ln1a.x as f64 + t * dx1) as i64;
1039        ip.y = (ln1a.y as f64 + t * dy1) as i64;
1040    }
1041
1042    true
1043}
1044
1045/// Calculate intersection point between two line segments for PointD (floating-point)
1046/// Direct port from clipper.core.h line 952 (floating-point specialization)
1047#[inline]
1048pub fn get_segment_intersect_pt_d(
1049    ln1a: PointD,
1050    ln1b: PointD,
1051    ln2a: PointD,
1052    ln2b: PointD,
1053    ip: &mut PointD,
1054) -> bool {
1055    // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
1056    let dx1 = ln1b.x - ln1a.x;
1057    let dy1 = ln1b.y - ln1a.y;
1058    let dx2 = ln2b.x - ln2a.x;
1059    let dy2 = ln2b.y - ln2a.y;
1060
1061    let det = dy1 * dx2 - dy2 * dx1;
1062    if det == 0.0 {
1063        return false;
1064    }
1065    let t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det;
1066    if t <= 0.0 {
1067        *ip = ln1a;
1068    } else if t >= 1.0 {
1069        *ip = ln1b;
1070    } else {
1071        ip.x = ln1a.x + t * dx1;
1072        ip.y = ln1a.y + t * dy1;
1073    }
1074    true
1075}
1076
1077/// Test if three points are collinear (lie on the same straight line)
1078/// Direct port from clipper.core.h line 796 - simplified version for Point64
1079#[inline]
1080pub fn is_collinear(pt1: Point64, shared_pt: Point64, pt2: Point64) -> bool {
1081    let a = shared_pt.x - pt1.x;
1082    let b = pt2.y - shared_pt.y;
1083    let c = shared_pt.y - pt1.y;
1084    let d = pt2.x - shared_pt.x;
1085
1086    // When checking for collinearity with very large coordinate values
1087    // ProductsAreEqual is more accurate than using CrossProduct
1088    products_are_equal(a, b, c, d)
1089}
1090
1091/// Determine if a point is inside, on the boundary, or outside a polygon
1092/// Direct port from clipper.core.h line 1049 - simplified version for Point64
1093/// Uses the winding number algorithm with proper edge case handling
1094#[inline]
1095pub fn point_in_polygon(pt: Point64, polygon: &Path64) -> PointInPolygonResult {
1096    if polygon.len() < 3 {
1097        return PointInPolygonResult::IsOutside;
1098    }
1099
1100    let mut val = 0i32;
1101    let mut first_idx = 0;
1102
1103    // Find first vertex with different y coordinate than pt
1104    while first_idx < polygon.len() && polygon[first_idx].y == pt.y {
1105        first_idx += 1;
1106    }
1107
1108    if first_idx == polygon.len() {
1109        // Not a proper polygon - all points have same y as test point
1110        return PointInPolygonResult::IsOutside;
1111    }
1112
1113    let mut is_above = polygon[first_idx].y < pt.y;
1114    let starting_above = is_above;
1115    let mut curr_idx = (first_idx + 1) % polygon.len();
1116    let mut wrap_to_start = false;
1117
1118    loop {
1119        // Handle wrapping around the polygon
1120        if curr_idx == polygon.len() {
1121            if wrap_to_start || first_idx == 0 {
1122                break;
1123            }
1124            wrap_to_start = true;
1125            curr_idx = 0;
1126        }
1127
1128        // Skip vertices while moving in same direction relative to pt.y
1129        if is_above {
1130            while curr_idx != polygon.len()
1131                && (!wrap_to_start || curr_idx < first_idx)
1132                && polygon[curr_idx].y < pt.y
1133            {
1134                curr_idx += 1;
1135            }
1136            if curr_idx == polygon.len() || (wrap_to_start && curr_idx >= first_idx) {
1137                continue;
1138            }
1139        } else {
1140            while curr_idx != polygon.len()
1141                && (!wrap_to_start || curr_idx < first_idx)
1142                && polygon[curr_idx].y > pt.y
1143            {
1144                curr_idx += 1;
1145            }
1146            if curr_idx == polygon.len() || (wrap_to_start && curr_idx >= first_idx) {
1147                continue;
1148            }
1149        }
1150
1151        let curr = &polygon[curr_idx];
1152        let prev_idx = if curr_idx == 0 {
1153            polygon.len() - 1
1154        } else {
1155            curr_idx - 1
1156        };
1157        let prev = &polygon[prev_idx];
1158
1159        if curr.y == pt.y {
1160            if curr.x == pt.x || (curr.y == prev.y && ((pt.x < prev.x) != (pt.x < curr.x))) {
1161                return PointInPolygonResult::IsOn;
1162            }
1163            curr_idx += 1;
1164            if curr_idx == first_idx {
1165                break;
1166            }
1167            continue;
1168        }
1169
1170        if pt.x < curr.x && pt.x < prev.x {
1171            // We're only interested in edges crossing on the left
1172        } else if pt.x > prev.x && pt.x > curr.x {
1173            val = 1 - val; // Toggle val
1174        } else {
1175            let d = cross_product_three_points(*prev, *curr, pt);
1176            if d == 0.0 {
1177                return PointInPolygonResult::IsOn;
1178            }
1179            if (d < 0.0) == is_above {
1180                val = 1 - val;
1181            }
1182        }
1183
1184        is_above = !is_above;
1185        curr_idx += 1;
1186    }
1187
1188    // Final edge case check
1189    if is_above != starting_above {
1190        let curr_idx = if curr_idx >= polygon.len() {
1191            0
1192        } else {
1193            curr_idx
1194        };
1195        let prev_idx = if curr_idx == 0 {
1196            polygon.len() - 1
1197        } else {
1198            curr_idx - 1
1199        };
1200        let curr = &polygon[curr_idx];
1201        let prev = &polygon[prev_idx];
1202        let d = cross_product_three_points(*prev, *curr, pt);
1203        if d == 0.0 {
1204            return PointInPolygonResult::IsOn;
1205        }
1206        if (d < 0.0) == is_above {
1207            val = 1 - val;
1208        }
1209    }
1210
1211    if val == 0 {
1212        PointInPolygonResult::IsOutside
1213    } else {
1214        PointInPolygonResult::IsInside
1215    }
1216}
1217
1218/// Get bounding rectangle for a single path - using existing implementation
1219/// Direct port from clipper.core.h line 432 (template function)
1220pub use get_bounds_path as get_bounds;
1221
1222/// Trait for converting from f64 with appropriate rounding behavior
1223/// Matches C++ Point<T> constructor semantics: rounds for integral types, casts for float
1224pub trait FromF64: Copy {
1225    fn from_f64(val: f64) -> Self;
1226    fn is_integral() -> bool;
1227}
1228
1229impl FromF64 for i64 {
1230    #[inline]
1231    fn from_f64(val: f64) -> Self {
1232        val.round() as i64
1233    }
1234    #[inline]
1235    fn is_integral() -> bool {
1236        true
1237    }
1238}
1239
1240impl FromF64 for f64 {
1241    #[inline]
1242    fn from_f64(val: f64) -> Self {
1243        val
1244    }
1245    #[inline]
1246    fn is_integral() -> bool {
1247        false
1248    }
1249}
1250
1251/// Scale a rectangle with type conversion
1252/// Direct port from clipper.core.h line 405
1253#[inline]
1254pub fn scale_rect<T1, T2>(rect: &Rect<T2>, scale: f64) -> Rect<T1>
1255where
1256    T1: Num + Copy + PartialOrd + FromF64,
1257    T2: Copy + ToF64,
1258{
1259    Rect {
1260        left: T1::from_f64(rect.left.to_f64() * scale),
1261        top: T1::from_f64(rect.top.to_f64() * scale),
1262        right: T1::from_f64(rect.right.to_f64() * scale),
1263        bottom: T1::from_f64(rect.bottom.to_f64() * scale),
1264    }
1265}
1266
1267/// Scale a path with type conversion and separate x/y scales
1268/// Direct port from clipper.core.h line 523
1269#[inline]
1270pub fn scale_path<T1, T2>(
1271    path: &Path<T2>,
1272    scale_x: f64,
1273    scale_y: f64,
1274    error_code: &mut i32,
1275) -> Path<T1>
1276where
1277    T1: Num + Copy + PartialOrd + FromF64,
1278    T2: Copy + ToF64,
1279{
1280    let mut sx = scale_x;
1281    let mut sy = scale_y;
1282
1283    if sx == 0.0 || sy == 0.0 {
1284        *error_code |= errors::SCALE_ERROR_I;
1285        // if no exception, treat as non-fatal error
1286        if sx == 0.0 {
1287            sx = 1.0;
1288        }
1289        if sy == 0.0 {
1290            sy = 1.0;
1291        }
1292    }
1293
1294    let mut result = Vec::with_capacity(path.len());
1295    for pt in path {
1296        result.push(Point {
1297            x: T1::from_f64(pt.x.to_f64() * sx),
1298            y: T1::from_f64(pt.y.to_f64() * sy),
1299        });
1300    }
1301    result
1302}
1303
1304/// Scale a path with uniform scale
1305/// Direct port from clipper.core.h line 550
1306#[inline]
1307pub fn scale_path_uniform<T1, T2>(path: &Path<T2>, scale: f64, error_code: &mut i32) -> Path<T1>
1308where
1309    T1: Num + Copy + PartialOrd + FromF64,
1310    T2: Copy + ToF64,
1311{
1312    scale_path(path, scale, scale, error_code)
1313}
1314
1315/// Scale multiple paths with type conversion and separate x/y scales
1316/// Includes range checking for integral output types
1317/// Direct port from clipper.core.h line 557
1318#[inline]
1319pub fn scale_paths<T1, T2>(
1320    paths: &Paths<T2>,
1321    scale_x: f64,
1322    scale_y: f64,
1323    error_code: &mut i32,
1324) -> Paths<T1>
1325where
1326    T1: Num + Copy + PartialOrd + FromF64,
1327    T2: Copy + ToF64,
1328{
1329    // Range check for integral output types
1330    if T1::is_integral() {
1331        // Compute bounds using ToF64 trait
1332        let mut xmin = f64::MAX;
1333        let mut ymin = f64::MAX;
1334        let mut xmax = f64::MIN;
1335        let mut ymax = f64::MIN;
1336        for path in paths {
1337            for p in path {
1338                let x = p.x.to_f64();
1339                let y = p.y.to_f64();
1340                if x < xmin {
1341                    xmin = x;
1342                }
1343                if x > xmax {
1344                    xmax = x;
1345                }
1346                if y < ymin {
1347                    ymin = y;
1348                }
1349                if y > ymax {
1350                    ymax = y;
1351                }
1352            }
1353        }
1354        if (xmin * scale_x) < constants::MIN_COORD_D
1355            || (xmax * scale_x) > constants::MAX_COORD_D
1356            || (ymin * scale_y) < constants::MIN_COORD_D
1357            || (ymax * scale_y) > constants::MAX_COORD_D
1358        {
1359            *error_code |= errors::RANGE_ERROR_I;
1360            return Paths::new(); // empty path
1361        }
1362    }
1363
1364    let mut result = Vec::with_capacity(paths.len());
1365    for path in paths {
1366        result.push(scale_path(path, scale_x, scale_y, error_code));
1367    }
1368    result
1369}
1370
1371/// Scale multiple paths with uniform scale
1372/// Direct port from clipper.core.h line 584
1373#[inline]
1374pub fn scale_paths_uniform<T1, T2>(paths: &Paths<T2>, scale: f64, error_code: &mut i32) -> Paths<T1>
1375where
1376    T1: Num + Copy + PartialOrd + FromF64,
1377    T2: Copy + ToF64,
1378{
1379    scale_paths(paths, scale, scale, error_code)
1380}
1381
1382/// Transform a path between types (no scaling, just type conversion)
1383/// Direct port from clipper.core.h line 591
1384#[inline]
1385pub fn transform_path<T1, T2>(path: &Path<T2>) -> Path<T1>
1386where
1387    T1: Num + Copy + FromF64,
1388    T2: Copy + ToF64,
1389{
1390    let mut result = Vec::with_capacity(path.len());
1391    for pt in path {
1392        result.push(Point {
1393            x: T1::from_f64(pt.x.to_f64()),
1394            y: T1::from_f64(pt.y.to_f64()),
1395        });
1396    }
1397    result
1398}
1399
1400/// Transform multiple paths between types (no scaling, just type conversion)
1401/// Direct port from clipper.core.h line 601
1402#[inline]
1403pub fn transform_paths<T1, T2>(paths: &Paths<T2>) -> Paths<T1>
1404where
1405    T1: Num + Copy + FromF64,
1406    T2: Copy + ToF64,
1407{
1408    let mut result = Vec::with_capacity(paths.len());
1409    for path in paths {
1410        result.push(transform_path(path));
1411    }
1412    result
1413}
1414
1415/// Check if two points are near each other within a distance threshold
1416/// Direct port from clipper.core.h line 616
1417#[inline]
1418pub fn near_equal<T>(p1: &Point<T>, p2: &Point<T>, max_dist_sqrd: f64) -> bool
1419where
1420    T: Copy + ToF64,
1421{
1422    sqr(p1.x.to_f64() - p2.x.to_f64()) + sqr(p1.y.to_f64() - p2.y.to_f64()) < max_dist_sqrd
1423}
1424
1425/// Strip near-equal consecutive points from a path
1426/// Direct port from clipper.core.h line 623
1427#[inline]
1428pub fn strip_near_equal<T>(path: &Path<T>, max_dist_sqrd: f64, is_closed_path: bool) -> Path<T>
1429where
1430    T: Copy + ToF64 + PartialEq + Num,
1431{
1432    if path.is_empty() {
1433        return Path::new();
1434    }
1435
1436    let mut result = Vec::with_capacity(path.len());
1437    let first_pt = path[0];
1438    let mut last_pt = first_pt;
1439    result.push(first_pt);
1440
1441    for pt in path.iter().skip(1) {
1442        if !near_equal(pt, &last_pt, max_dist_sqrd) {
1443            last_pt = *pt;
1444            result.push(last_pt);
1445        }
1446    }
1447
1448    if !is_closed_path {
1449        return result;
1450    }
1451
1452    while result.len() > 1 && near_equal(result.last().unwrap(), &first_pt, max_dist_sqrd) {
1453        result.pop();
1454    }
1455
1456    result
1457}
1458
1459/// Strip near-equal consecutive points from multiple paths
1460/// Direct port from clipper.core.h line 647
1461#[inline]
1462pub fn strip_near_equal_paths<T>(
1463    paths: &Paths<T>,
1464    max_dist_sqrd: f64,
1465    is_closed_path: bool,
1466) -> Paths<T>
1467where
1468    T: Copy + ToF64 + PartialEq + Num,
1469{
1470    let mut result = Vec::with_capacity(paths.len());
1471    for path in paths {
1472        result.push(strip_near_equal(path, max_dist_sqrd, is_closed_path));
1473    }
1474    result
1475}
1476
1477/// Translate a point by dx, dy
1478/// Direct port from clipper.core.h line 975
1479#[inline]
1480pub fn translate_point<T>(pt: &Point<T>, dx: f64, dy: f64) -> Point<T>
1481where
1482    T: Copy + ToF64 + FromF64 + Num,
1483{
1484    Point {
1485        x: T::from_f64(pt.x.to_f64() + dx),
1486        y: T::from_f64(pt.y.to_f64() + dy),
1487    }
1488}
1489
1490/// Reflect a point about a pivot point
1491/// Direct port from clipper.core.h line 986
1492#[inline]
1493pub fn reflect_point<T>(pt: &Point<T>, pivot: &Point<T>) -> Point<T>
1494where
1495    T: Num + Copy,
1496{
1497    Point {
1498        x: pivot.x + (pivot.x - pt.x),
1499        y: pivot.y + (pivot.y - pt.y),
1500    }
1501}
1502
1503/// Get the sign of a value: -1, 0, or 1
1504/// Direct port from clipper.core.h line 996
1505#[inline]
1506pub fn get_sign<T>(val: &T) -> i32
1507where
1508    T: Num + PartialOrd,
1509{
1510    if val.is_zero() {
1511        0
1512    } else if *val > T::zero() {
1513        1
1514    } else {
1515        -1
1516    }
1517}
1518
1519/// Get the sign of the cross product of vectors (pt1->pt2) and (pt2->pt3)
1520/// Uses high-precision 128-bit arithmetic for exact results with i64 coordinates
1521/// Direct port from clipper.core.h line 753
1522#[inline]
1523pub fn cross_product_sign(pt1: Point64, pt2: Point64, pt3: Point64) -> i32 {
1524    // Use i128 for exact arithmetic (available on all Rust platforms)
1525    let a = pt2.x as i128 - pt1.x as i128;
1526    let b = pt3.y as i128 - pt2.y as i128;
1527    let c = pt2.y as i128 - pt1.y as i128;
1528    let d = pt3.x as i128 - pt2.x as i128;
1529
1530    let ab = a * b;
1531    let cd = c * d;
1532
1533    if ab > cd {
1534        1
1535    } else if ab < cd {
1536        -1
1537    } else {
1538        0
1539    }
1540}
1541
1542/// Check if two line segments intersect
1543/// Direct port from clipper.core.h line 1003
1544#[inline]
1545pub fn segments_intersect(
1546    seg1a: Point64,
1547    seg1b: Point64,
1548    seg2a: Point64,
1549    seg2b: Point64,
1550    inclusive: bool,
1551) -> bool {
1552    if inclusive {
1553        let res1 = cross_product_three_points(seg1a, seg2a, seg2b);
1554        let res2 = cross_product_three_points(seg1b, seg2a, seg2b);
1555        if res1 * res2 > 0.0 {
1556            return false;
1557        }
1558        let res3 = cross_product_three_points(seg2a, seg1a, seg1b);
1559        let res4 = cross_product_three_points(seg2b, seg1a, seg1b);
1560        if res3 * res4 > 0.0 {
1561            return false;
1562        }
1563        // ensures not collinear
1564        res1 != 0.0 || res2 != 0.0 || res3 != 0.0 || res4 != 0.0
1565    } else {
1566        (get_sign(&cross_product_three_points(seg1a, seg2a, seg2b))
1567            * get_sign(&cross_product_three_points(seg1b, seg2a, seg2b))
1568            < 0)
1569            && (get_sign(&cross_product_three_points(seg2a, seg1a, seg1b))
1570                * get_sign(&cross_product_three_points(seg2b, seg1a, seg1b))
1571                < 0)
1572    }
1573}
1574
1575/// Get the closest point on a segment to an off-segment point
1576/// Direct port from clipper.core.h line 1024
1577#[inline]
1578pub fn get_closest_point_on_segment<T>(off_pt: Point<T>, seg1: Point<T>, seg2: Point<T>) -> Point<T>
1579where
1580    T: Copy + ToF64 + FromF64 + Num + PartialEq,
1581{
1582    if seg1.x == seg2.x && seg1.y == seg2.y {
1583        return seg1;
1584    }
1585    let dx = seg2.x.to_f64() - seg1.x.to_f64();
1586    let dy = seg2.y.to_f64() - seg1.y.to_f64();
1587    let mut q =
1588        (off_pt.x.to_f64() - seg1.x.to_f64()) * dx + (off_pt.y.to_f64() - seg1.y.to_f64()) * dy;
1589    q /= sqr(dx) + sqr(dy);
1590    q = q.clamp(0.0, 1.0);
1591    Point {
1592        x: T::from_f64(seg1.x.to_f64() + q * dx),
1593        y: T::from_f64(seg1.y.to_f64() + q * dy),
1594    }
1595}
1596
1597/// Perpendicular distance squared from a point to a line defined by two points
1598/// Direct port from clipper.core.h line 840
1599/// Uses the formula: (Ax3 + By3 + C)^2 / (A^2 + B^2)
1600/// where A, B, C define the line equation
1601#[inline]
1602pub fn perpendic_dist_from_line_sqrd<T>(pt: Point<T>, line1: Point<T>, line2: Point<T>) -> f64
1603where
1604    T: Copy + ToF64,
1605{
1606    let a = pt.x.to_f64() - line1.x.to_f64();
1607    let b = pt.y.to_f64() - line1.y.to_f64();
1608    let c = line2.x.to_f64() - line1.x.to_f64();
1609    let d = line2.y.to_f64() - line1.y.to_f64();
1610    if c == 0.0 && d == 0.0 {
1611        return 0.0;
1612    }
1613    sqr(a * d - c * b) / (c * c + d * d)
1614}
1615
1616/// Generate an elliptical path around a center point
1617/// Direct port from clipper.h line 613 (Ellipse template function)
1618pub fn ellipse_point64(center: Point64, radius_x: f64, radius_y: f64, steps: usize) -> Path64 {
1619    if radius_x <= 0.0 {
1620        return Path64::new();
1621    }
1622    let radius_y = if radius_y <= 0.0 { radius_x } else { radius_y };
1623    let steps = if steps <= 2 {
1624        (constants::PI * ((radius_x + radius_y) / 2.0).sqrt()) as usize
1625    } else {
1626        steps
1627    };
1628    if steps == 0 {
1629        return Path64::new();
1630    }
1631
1632    let si = (2.0 * constants::PI / steps as f64).sin();
1633    let co = (2.0 * constants::PI / steps as f64).cos();
1634    let mut dx = co;
1635    let mut dy = si;
1636    let mut result = Path64::with_capacity(steps);
1637    result.push(Point64::new(
1638        (center.x as f64 + radius_x).round() as i64,
1639        center.y,
1640    ));
1641    for _ in 1..steps {
1642        result.push(Point64::new(
1643            (center.x as f64 + radius_x * dx).round() as i64,
1644            (center.y as f64 + radius_y * dy).round() as i64,
1645        ));
1646        let x = dx * co - dy * si;
1647        dy = dy * co + dx * si;
1648        dx = x;
1649    }
1650    result
1651}
1652
1653/// Generate an elliptical path around a center point (PointD version)
1654/// Direct port from clipper.h line 613 (Ellipse template function)
1655pub fn ellipse_point_d(center: PointD, radius_x: f64, radius_y: f64, steps: usize) -> PathD {
1656    if radius_x <= 0.0 {
1657        return PathD::new();
1658    }
1659    let radius_y = if radius_y <= 0.0 { radius_x } else { radius_y };
1660    let steps = if steps <= 2 {
1661        (constants::PI * ((radius_x + radius_y) / 2.0).sqrt()) as usize
1662    } else {
1663        steps
1664    };
1665    if steps == 0 {
1666        return PathD::new();
1667    }
1668
1669    let si = (2.0 * constants::PI / steps as f64).sin();
1670    let co = (2.0 * constants::PI / steps as f64).cos();
1671    let mut dx = co;
1672    let mut dy = si;
1673    let mut result = PathD::with_capacity(steps);
1674    result.push(PointD::new(center.x + radius_x, center.y));
1675    for _ in 1..steps {
1676        result.push(PointD::new(
1677            center.x + radius_x * dx,
1678            center.y + radius_y * dy,
1679        ));
1680        let x = dx * co - dy * si;
1681        dy = dy * co + dx * si;
1682        dx = x;
1683    }
1684    result
1685}
1686
1687// Include tests from separate file
1688#[cfg(test)]
1689#[path = "core_tests.rs"]
1690mod tests;