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::param::SegmentParam;
use crate::kernel::int::curve::segment::Segment;
use i_overlay::i_shape::int::IntPoint;

pub(crate) trait PointAt<I: CurveInt> {
    fn point_at(&self, param: SegmentParam<I>) -> IntPoint<I>;
}

impl<I: CurveInt> PointAt<I> for ArcSegment<I> {
    #[inline(always)]
    fn point_at(&self, param: SegmentParam<I>) -> IntPoint<I> {
        ArcSegment::point_at(self, param)
    }
}

impl<I: CurveInt> PointAt<I> for Segment<I> {
    #[inline(always)]
    fn point_at(&self, param: SegmentParam<I>) -> IntPoint<I> {
        match self {
            Segment::Line(line) => line.control_points.point_at(param),
            Segment::Quad(quad) => quad.control_points.point_at(param),
            Segment::Cubic(cubic) => cubic.control_points.point_at(param),
            Segment::Arc(arc) => arc.point_at(param),
        }
    }
}

impl<I: CurveInt> PointAt<I> for [IntPoint<I>; 2] {
    #[inline(always)]
    fn point_at(&self, t: SegmentParam<I>) -> IntPoint<I> {
        let [p0, p1] = *self;
        p0 + t.scale_vector_to_point(p1 - p0)
    }
}

impl<I: CurveInt> PointAt<I> for [IntPoint<I>; 3] {
    #[inline(always)]
    fn point_at(&self, t: SegmentParam<I>) -> IntPoint<I> {
        let [p0, p1, p2] = *self;
        let p01 = [p0, p1].point_at(t);
        let p12 = [p1, p2].point_at(t);
        [p01, p12].point_at(t)
    }
}

impl<I: CurveInt> PointAt<I> for [IntPoint<I>; 4] {
    #[inline(always)]
    fn point_at(&self, t: SegmentParam<I>) -> IntPoint<I> {
        let [p0, p1, p2, p3] = *self;
        let p01 = [p0, p1].point_at(t);
        let p12 = [p1, p2].point_at(t);
        let p23 = [p2, p3].point_at(t);

        let p012 = [p01, p12].point_at(t);
        let p123 = [p12, p23].point_at(t);

        [p012, p123].point_at(t)
    }
}

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

    #[test]
    fn line_point_at_uses_rounded_scale() {
        let line = [IntPoint::new(0, 0), IntPoint::new(1, -1)];

        assert_eq!(line.point_at(SegmentParam::half()), IntPoint::new(1, -1));
    }

    #[test]
    fn quad_point_at_uses_rounded_de_casteljau_steps() {
        let quad = [IntPoint::new(0, 0), IntPoint::new(2, 0), IntPoint::new(2, 2)];

        assert_eq!(quad.point_at(SegmentParam::half()), IntPoint::new(2, 1));
    }

    #[test]
    fn cubic_point_at_uses_rounded_de_casteljau_steps() {
        let cubic = [
            IntPoint::new(0, 0),
            IntPoint::new(4, 0),
            IntPoint::new(4, 4),
            IntPoint::new(8, 4),
        ];

        assert_eq!(cubic.point_at(SegmentParam::half()), IntPoint::new(4, 2));
    }

    #[test]
    fn segment_dispatches_arc_point_at() {
        let one = FixedScale::<i32>::DENOMINATOR as i32;
        let 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: [
                IntPoint::new(100, 0),
                IntPoint::new(100, 100),
                IntPoint::new(0, 100),
            ],
            weights: [one, 759_250_125, one],
            start_phase: ArcPhase { cos: one, sin: 0 },
            end_phase: ArcPhase { cos: 0, sin: one },
            direction: ArcDirection::CounterClockwise,
        };
        let segment = Segment::Arc(arc);

        assert_eq!(
            segment.point_at(SegmentParam::half()),
            arc.point_at(SegmentParam::half())
        );
        assert_eq!(segment.point_at(SegmentParam::half()), IntPoint::new(71, 71));
    }
}