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::int::bool::edge::CurveEdge;
use crate::kernel::int::curve::chord::Chord;
use crate::kernel::int::curve::param::{SegmentParam, interpolate_segment_param};
use alloc::vec::Vec;
use i_key_sort::sort::one_key_cmp::OneKeyAndCmpSort;
use i_overlay::i_float::int::number::wide_int::WideIntNumber;
use i_overlay::i_shape::int::IntPoint;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CurveSplitMark<I: CurveInt> {
    pub(crate) edge_index: usize,
    pub(crate) point: IntPoint<I>,
    pub(crate) param: SegmentParam<I>,
}

impl<I: CurveInt> CurveSplitMark<I> {
    #[inline]
    pub(crate) fn push_if_interior(
        marks: &mut Vec<Self>,
        edge_index: usize,
        point: IntPoint<I>,
        param: SegmentParam<I>,
    ) {
        let value = param.value();
        if value > I::Wide::ZERO && value < SegmentParam::<I>::DENOMINATOR {
            marks.push(Self {
                edge_index,
                point,
                param,
            });
        }
    }

    pub(crate) fn sort_and_dedup(marks: &mut Vec<Self>, buffer: &mut Vec<Self>) {
        marks.sort_by_one_key_then_by_and_buffer(
            false,
            buffer,
            |m| m.edge_index,
            |m0, m1| {
                m0.param
                    .value()
                    .cmp(&m1.param.value())
                    .then_with(|| m0.point.cmp(&m1.point))
            },
        );
        marks.dedup();
    }
}

pub(crate) struct CurveEdgeSplitter<I: CurveInt> {
    edge_buffer: Vec<CurveEdge<I>>,
}

impl<I: CurveInt> CurveEdgeSplitter<I> {
    pub(crate) fn new() -> Self {
        Self {
            edge_buffer: Vec::new(),
        }
    }

    pub(crate) fn split(&mut self, edges: &mut Vec<CurveEdge<I>>, marks: &[CurveSplitMark<I>]) {
        if marks.is_empty() {
            return;
        }

        self.edge_buffer.clear();
        self.edge_buffer.reserve(marks.len());

        let mut mark_index = 0;
        for (edge_index, &edge) in edges.iter().enumerate() {
            let start = mark_index;
            while mark_index < marks.len() && marks[mark_index].edge_index == edge_index {
                mark_index += 1;
            }

            if start == mark_index {
                self.edge_buffer.push(edge);
            } else {
                Self::split_edge(edge, &marks[start..mark_index], &mut self.edge_buffer);
            }
        }

        core::mem::swap(edges, &mut self.edge_buffer);
    }

    fn split_edge(edge: CurveEdge<I>, marks: &[CurveSplitMark<I>], output: &mut Vec<CurveEdge<I>>) {
        let source_start = edge.start_param;
        let source_end = edge.end_param;

        let mut remaining = edge.curve;
        let mut previous = I::Wide::ZERO;
        let mut remaining_start = source_start;

        for mark in marks {
            let value = mark.param.value();
            if value <= previous || value >= SegmentParam::<I>::DENOMINATOR {
                continue;
            }

            let numerator = value - previous;
            let denominator = SegmentParam::<I>::DENOMINATOR - previous;
            let local_param = SegmentParam::from_int(I::from_wide(numerator), I::from_wide(denominator));
            let [left, right] = remaining.split_at_point(local_param, mark.point);
            let source_param = interpolate_segment_param(source_start, source_end, mark.param);

            if !left.chord().is_zero_length() {
                output.push(CurveEdge::new(left, edge.curve_id, remaining_start, source_param));
            }

            remaining = right;
            previous = value;
            remaining_start = source_param;
        }

        if !remaining.chord().is_zero_length() {
            output.push(CurveEdge::new(
                remaining,
                edge.curve_id,
                remaining_start,
                source_end,
            ));
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::int::bool::source::CurveId;
    use crate::kernel::int::curve::line::LineSegment;
    use crate::kernel::int::curve::segment::Segment;
    use alloc::vec;

    #[test]
    fn composes_local_params_into_source_edges() {
        let curve = Segment::Line(LineSegment {
            control_points: [IntPoint::new(0, 0), IntPoint::new(100, 0)],
        });
        let mut edges = vec![CurveEdge::full(curve, CurveId(0))];
        let mut splitter = CurveEdgeSplitter::new();

        splitter.split(
            &mut edges,
            &[CurveSplitMark {
                edge_index: 0,
                point: IntPoint::new(50, 0),
                param: SegmentParam::half(),
            }],
        );
        splitter.split(
            &mut edges,
            &[CurveSplitMark {
                edge_index: 1,
                point: IntPoint::new(75, 0),
                param: SegmentParam::half(),
            }],
        );

        assert_eq!(edges.len(), 3);
        assert_eq!(edges[0].end_param, SegmentParam::from_int(1, 2));
        assert_eq!(edges[1].start_param, SegmentParam::from_int(1, 2));
        assert_eq!(edges[1].end_param, SegmentParam::from_int(3, 4));
        assert_eq!(edges[2].start_param, SegmentParam::from_int(3, 4));
    }

    #[test]
    fn uses_edge_source_range_when_point_mark_is_ambiguous() {
        let edge_curve = Segment::Line(LineSegment {
            control_points: [IntPoint::new(50, 0), IntPoint::new(100, 0)],
        });
        let mut edges = vec![CurveEdge::new(
            edge_curve,
            CurveId(0),
            SegmentParam::half(),
            SegmentParam::new(SegmentParam::<i32>::DENOMINATOR as i32),
        )];
        let mut splitter = CurveEdgeSplitter::new();

        splitter.split(
            &mut edges,
            &[CurveSplitMark {
                edge_index: 0,
                point: IntPoint::new(75, 0),
                param: SegmentParam::half(),
            }],
        );

        assert_eq!(edges[0].start_param, SegmentParam::half());
        assert_eq!(edges[0].end_param, SegmentParam::from_int(3, 4));
        assert_eq!(edges[1].start_param, SegmentParam::from_int(3, 4));
    }
}