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::chord::Chord;
use crate::kernel::int::curve::param::SegmentParam;
use crate::kernel::int::curve::segment::Segment;
use crate::kernel::int::math::angle::ApproximateAngle;
use i_overlay::i_float::int::number::wide_int::WideIntNumber;
use i_overlay::i_shape::int::IntPoint;

pub(super) struct Split<I: CurveInt> {
    pub(super) t0: SegmentParam<I>,
    pub(super) s0: Option<Segment<I>>,
    pub(super) t1: SegmentParam<I>,
    pub(super) s1: Option<Segment<I>>,
    pub(super) step: SegmentParam<I>,
}

impl<I: CurveInt> Segment<I> {
    pub(crate) fn is_nearly_linear(&self, sin_angle_neg_pow2: u32) -> bool {
        let chord = self.chord();
        let chord_vector = chord.vector();
        if chord_vector.sqr_length() == I::Wide::ZERO {
            return false;
        }

        match self {
            Segment::Line(_) => true,
            Segment::Quad(quad) => {
                let [p0, p1, p2] = quad.control_points;
                let chord = p0 - p2;
                let h0 = chord.is_nearly_collinear_with(p0 - p1, sin_angle_neg_pow2);
                let h1 = chord.is_nearly_collinear_with(p2 - p1, sin_angle_neg_pow2);

                h0 && h1
            }
            Segment::Cubic(cubic) => {
                let [p0, p1, p2, p3] = cubic.control_points;
                let chord = p0 - p3;
                let h0 = chord.is_nearly_collinear_with(p0 - p1, sin_angle_neg_pow2);
                let h1 = chord.is_nearly_collinear_with(p3 - p2, sin_angle_neg_pow2);

                h0 && h1
            }
            Segment::Arc(arc) => {
                let [p0, p1, p2] = arc.control_points;
                let chord = p0 - p2;
                let h0 = chord.is_nearly_collinear_with(p0 - p1, sin_angle_neg_pow2);
                let h1 = chord.is_nearly_collinear_with(p2 - p1, sin_angle_neg_pow2);

                h0 && h1
            }
        }
    }

    #[inline]
    pub(super) fn split(
        &self,
        t: SegmentParam<I>,
        step: SegmentParam<I>,
        a: IntPoint<I>,
        b: IntPoint<I>,
    ) -> Split<I> {
        let half_step = step.value() >> 1;
        let step = SegmentParam::new(I::from_wide(half_step));
        let t0 = SegmentParam::new(I::from_wide(t.value() - half_step));
        let t1 = SegmentParam::new(I::from_wide(t.value() + half_step));

        let [s0, s1] = self.bisect(a, b, SegmentParam::half());

        Split { t0, s0, t1, s1, step }
    }
}
#[cfg(test)]
mod tests {
    use super::Segment;
    use crate::kernel::int::curve::arc::{ArcDirection, ArcPhase, ArcSegment, ArcVector, EllipseFrame};
    use crate::kernel::int::curve::cubic::CubicSegment;
    use crate::kernel::int::curve::quad::QuadSegment;
    use i_overlay::i_float::int::number::fixed_scale::FixedScale;
    use i_overlay::i_shape::int::IntPoint;

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

        Segment::Arc(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 detects_nearly_linear_segment() {
        let segment = Segment::Cubic(CubicSegment {
            control_points: [[0, 0].into(), [8, 0].into(), [24, 1].into(), [32, 0].into()],
        });
        assert!(segment.is_nearly_linear(3));
    }

    #[test]
    fn rejects_curved_segment() {
        let segment = Segment::Quad(QuadSegment {
            control_points: [[0, 0].into(), [4, 8].into(), [8, 0].into()],
        });
        assert!(!segment.is_nearly_linear(3));
    }

    #[test]
    fn detects_nearly_linear_arc_from_control_triangle() {
        let segment = arc_with_controls([IntPoint::new(0, 0), IntPoint::new(16, 1), IntPoint::new(32, 0)]);

        assert!(segment.is_nearly_linear(3));
    }

    #[test]
    fn rejects_curved_arc_control_triangle() {
        let segment = arc_with_controls([IntPoint::new(0, 0), IntPoint::new(16, 16), IntPoint::new(32, 0)]);

        assert!(!segment.is_nearly_linear(3));
    }
}