i_curve 0.1.2

Boolean operations on closed paths of lines, Bezier curves, and rational elliptic arcs
Documentation
use crate::int::CurveInt;
use crate::kernel::int::curve::arc::ArcSegment;
use crate::kernel::int::curve::line::LineSegment;
use crate::kernel::int::curve::segment::Segment;
use i_overlay::i_float::triangle::Triangle;

impl<I: CurveInt> ArcSegment<I> {
    #[inline]
    pub(crate) fn try_segment(self) -> Option<Segment<I>> {
        let [p0, p1, p2] = self.control_points;

        if p0 == p2 {
            None
        } else if Triangle::is_line(p0, p1, p2) {
            LineSegment {
                control_points: [p0, p2],
            }
            .try_segment()
        } else {
            debug_assert!(
                self.is_xy_monotone(),
                "kernel arcs must be split at every world-space extremum"
            );
            Some(Segment::Arc(self))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::kernel::int::curve::arc::{ArcDirection, ArcPhase, ArcVector, EllipseFrame};
    use i_overlay::i_float::int::number::fixed_scale::FixedScale;
    use i_overlay::i_shape::int::IntPoint;

    fn arc(control_points: [IntPoint<i32>; 3]) -> ArcSegment<i32> {
        let one = FixedScale::<i32>::DENOMINATOR as i32;

        ArcSegment {
            ellipse: EllipseFrame {
                center: IntPoint::new(0, 0),
                axis_x: ArcVector { x: 100, y: 0 },
                axis_y: ArcVector { x: 0, y: 100 },
            },
            control_points,
            weights: [one, 759_250_125, one],
            start_phase: ArcPhase { cos: one, sin: 0 },
            end_phase: ArcPhase { cos: 0, sin: one },
            direction: ArcDirection::CounterClockwise,
        }
    }

    #[test]
    fn drops_arc_with_collapsed_chord() {
        let point = IntPoint::new(10, 20);
        let segment = arc([point, IntPoint::new(12, 22), point]).try_segment();

        assert!(segment.is_none());
    }

    #[test]
    fn reduces_collinear_arc_to_line() {
        let p0 = IntPoint::new(0, 0);
        let p1 = IntPoint::new(5, 5);
        let p2 = IntPoint::new(10, 10);
        let segment = arc([p0, p1, p2]).try_segment();

        match segment {
            Some(Segment::Line(line)) => assert_eq!(line.control_points, [p0, p2]),
            _ => panic!("expected line segment"),
        }
    }

    #[test]
    fn keeps_monotone_non_degenerate_arc() {
        let source = arc([
            IntPoint::new(100, 0),
            IntPoint::new(100, 100),
            IntPoint::new(0, 100),
        ]);
        let segment = source.try_segment();

        match segment {
            Some(Segment::Arc(result)) => assert_eq!(result, source),
            _ => panic!("expected arc segment"),
        }
    }
}