klyff_msdf 0.1.3

MSDF generation library with optional GPU acceleration.
Documentation
use glam::Vec2;

use crate::{math_segment::Segment, polygon_clipping::EPS};

/// Tests if a segment contributes to the overall outline of a shape, if so then it's valid.
pub(super) fn is_valid_segment(
    segment_index: usize,
    segs: &[Segment],
    bvh_end_at: usize,
) -> bool {
    let segment = segs[segment_index];
    let midpoint = segment.from.midpoint(segment.to);
    let mut winding_num = 0.;

    // winding_contribution_x/y both early-return for non-crossing segments,
    // so iterating all segments is fine.
    let _ = bvh_end_at;
    for (i, seg) in segs.iter().enumerate() {
        if i == segment_index {
            continue;
        }
        winding_num += if (segment.from.y - segment.to.y).abs() < EPS {
            winding_contribution_y(midpoint, seg)
        } else {
            winding_contribution_x(midpoint, seg)
        };
    }

    let self_winding = if segment.from.y == segment.to.y {
        winding_contribution_y(midpoint - glam::vec2(0., 1.), &segment)
    } else {
        winding_contribution_x(midpoint - glam::vec2(1., 0.), &segment)
    };

    let wind_before = zero_one_or_minusone(winding_num);
    let wind_after = zero_one_or_minusone(winding_num + self_winding);

    wind_before != wind_after
}

fn zero_one_or_minusone(x: f32) -> f32 {
    if x > 0.1 {
        1.
    } else if x < -0.1 {
        -1.
    } else {
        0.
    }
}

fn winding_contribution_x(p: Vec2, seg: &Segment) -> f32 {
    const EPS: f32 = 1e-6;
    let mut a = seg.from;
    let mut b = seg.to;

    if a.y > b.y {
        std::mem::swap(&mut a, &mut b);
    }
    if (a.y - b.y).abs() < EPS {
        return 0.;
    }

    if p.y < a.y - EPS || p.y > b.y + EPS {
        return 0.;
    }
    let mult = if p.y <= a.y + EPS || p.y >= b.y - EPS {
        0.5
    } else {
        1.
    };

    let t = (p.y - a.y) / (b.y - a.y);
    let x_intersect = a.x + t * (b.x - a.x);

    if x_intersect <= p.x {
        return 0.;
    }

    let cross = (seg.to - seg.from).perp_dot(p - seg.from);
    if cross > 0.0 { mult } else { -mult }
}

fn winding_contribution_y(p: Vec2, seg: &Segment) -> f32 {
    const EPS: f32 = 1e-6;
    let mut a = seg.from;
    let mut b = seg.to;

    if a.x > b.x {
        std::mem::swap(&mut a, &mut b);
    }
    if (a.x - b.x).abs() < EPS {
        return 0.;
    }

    if p.x < a.x - EPS || p.x > b.x + EPS {
        return 0.;
    }
    let mult = if p.x <= a.x + EPS || p.x >= b.x - EPS {
        0.5
    } else {
        1.
    };

    let t = (p.x - a.x) / (b.x - a.x);
    let y_intersect = a.y + t * (b.y - a.y);

    if y_intersect <= p.y {
        return 0.;
    }

    let cross = (seg.to - seg.from).perp_dot(p - seg.from);
    if cross > 0.0 { mult } else { -mult }
}

#[cfg(test)]
mod test {
    use glam::{Vec2, vec2};

    use crate::{SegmentCollector, math_segment::Segment};

    fn make_polys(polys: Vec<Vec<Vec2>>) -> Vec<Segment> {
        let mut segs = vec![];
        let mut collector = SegmentCollector::new(&mut segs, 1.);
        for poly in polys {
            let mut point_iter = poly.into_iter();
            if let Some(first) = point_iter.next() {
                collector.move_to(first.x, first.y);
            }

            for point in point_iter {
                collector.line_to(point.x, point.y);
            }

            collector.close();
        }

        segs
    }
    #[test]
    fn valid_edge_check_contained() {
        let poly = make_polys(vec![
            vec![
                vec2(0.0, 0.0),
                vec2(0.0, 10.0),
                vec2(10.0, 10.0),
                vec2(10.0, 0.0),
            ],
            vec![vec2(1., 1.), vec2(1., 9.), vec2(9., 9.), vec2(9., 1.)],
        ]);
        for (i, s) in poly.iter().enumerate() {
            if s.from.x == 1. || s.from.x == 9. {
                assert!(
                    !super::is_valid_segment(i, &poly, poly.len()),
                    "Segment {s:?} failed"
                );
            } else {
                assert!(
                    super::is_valid_segment(i, &poly, poly.len()),
                    "Segment {s:?} failed"
                );
            }
        }
    }
}