use crate::Point;
#[derive(Default)]
pub(crate) struct Crossings {
winding: i32,
on_boundary: u32,
}
impl Crossings {
pub(crate) fn is_inside_non_zero(&self) -> bool {
self.winding != 0 || self.touches_boundary()
}
pub(crate) fn is_inside_even_odd(&self) -> bool {
(self.winding & 1) != 0 || self.touches_boundary()
}
fn touches_boundary(&self) -> bool {
self.on_boundary > 0
}
pub(crate) fn line(&mut self, from: Point, to: Point, at: Point) {
let mut low = from.y;
let mut high = to.y;
let mut direction = 1;
if low > high {
std::mem::swap(&mut low, &mut high);
direction = -1;
}
if at.y < low || at.y > high {
return;
}
if self.note_if_on_segment(at, from, to) {
return;
}
if at.y == high {
return;
}
let side = (to.x - from.x) * (at.y - from.y) - (to.y - from.y) * (at.x - from.x);
if side == 0.0 {
if at.x != to.x || at.y != to.y {
self.on_boundary += 1;
}
} else if side.signum() as i32 != direction {
self.winding += direction;
}
}
pub(crate) fn quad(&mut self, from: Point, control: Point, to: Point, at: Point) {
let mut pieces = [Point::ZERO; 5];
let split = chop_quad_at_y_extremum(&[from, control, to], &mut pieces);
self.monotonic_quad(&pieces[0..3], at);
if split {
self.monotonic_quad(&pieces[2..5], at);
}
}
pub(crate) fn cubic(&mut self, from: Point, first: Point, second: Point, to: Point, at: Point) {
let mut pieces = [Point::ZERO; 10];
let count = chop_cubic_at_y_extrema(&[from, first, second, to], &mut pieces);
for index in 0..=count {
self.monotonic_cubic(&pieces[index * 3..index * 3 + 4], at);
}
}
fn monotonic_quad(&mut self, points: &[Point], at: Point) {
let (start, control, end) = (points[0], points[1], points[2]);
let Some(direction) = self.enter_monotonic(start, end, at) else {
return;
};
let roots = unit_quadratic_roots(
start.y - 2.0 * control.y + end.y,
2.0 * (control.y - start.y),
start.y - at.y,
);
let x_at_crossing = match roots {
None => points[(1 - direction) as usize].x,
Some(t) => {
let c = start.x;
let a = end.x - 2.0 * control.x + c;
let b = 2.0 * (control.x - c);
(a * t + b) * t + c
}
};
self.settle(x_at_crossing, at, end, direction);
}
fn monotonic_cubic(&mut self, points: &[Point], at: Point) {
let (start, end) = (points[0], points[3]);
let Some(direction) = self.enter_monotonic(start, end, at) else {
return;
};
let (min_x, max_x) = points.iter().fold((f32::MAX, f32::MIN), |(lo, hi), p| {
(lo.min(p.x), hi.max(p.x))
});
if at.x < min_x {
return;
}
if at.x > max_x {
self.winding += direction;
return;
}
let t = monotonic_cubic_parameter_at_y(points, at.y);
let x_at_crossing = evaluate_cubic(points[0].x, points[1].x, points[2].x, points[3].x, t);
self.settle(x_at_crossing, at, end, direction);
}
fn enter_monotonic(&mut self, start: Point, end: Point, at: Point) -> Option<i32> {
let (mut low, mut high, mut direction) = (start.y, end.y, 1);
if low > high {
std::mem::swap(&mut low, &mut high);
direction = -1;
}
if at.y < low || at.y > high {
return None;
}
if self.note_if_on_segment(at, start, end) {
return None;
}
if at.y == high {
return None;
}
Some(direction)
}
fn settle(&mut self, x_at_crossing: f32, at: Point, end: Point, direction: i32) {
if nearly_equal(x_at_crossing, at.x) {
if at.x != end.x || at.y != end.y {
self.on_boundary += 1;
}
return;
}
if x_at_crossing < at.x {
self.winding += direction;
}
}
fn note_if_on_segment(&mut self, at: Point, start: Point, end: Point) -> bool {
let on_segment = if start.y == end.y {
is_between(start.x, at.x, end.x) && at.x != end.x
} else {
at.x == start.x && at.y == start.y
};
if on_segment {
self.on_boundary += 1;
}
on_segment
}
}
fn unit_quadratic_roots(a: f32, b: f32, c: f32) -> Option<f32> {
if a == 0.0 {
if b == 0.0 {
return None;
}
return in_unit_interval(-c / b);
}
let discriminant = b * b - 4.0 * a * c;
if discriminant < 0.0 {
return None;
}
let root = discriminant.sqrt();
let q = if b < 0.0 {
-(b - root) / 2.0
} else {
-(b + root) / 2.0
};
in_unit_interval(q / a).or_else(|| in_unit_interval(c / q))
}
fn in_unit_interval(t: f32) -> Option<f32> {
(t.is_finite() && (0.0..=1.0).contains(&t)).then_some(t)
}
fn chop_quad_at_y_extremum(points: &[Point; 3], out: &mut [Point; 5]) -> bool {
let (y0, y1, y2) = (points[0].y, points[1].y, points[2].y);
let denominator = y0 - 2.0 * y1 + y2;
let t = in_unit_interval(if denominator == 0.0 {
f32::NAN
} else {
(y0 - y1) / denominator
})
.filter(|t| *t > 0.0 && *t < 1.0);
match t {
None => {
out[0..3].copy_from_slice(points);
false
}
Some(t) => {
let ab = lerp(points[0], points[1], t);
let bc = lerp(points[1], points[2], t);
let mid = lerp(ab, bc, t);
out[0] = points[0];
out[1] = ab;
out[2] = mid;
out[3] = bc;
out[4] = points[2];
true
}
}
}
fn chop_cubic_at_y_extrema(points: &[Point; 4], out: &mut [Point; 10]) -> usize {
let (y0, y1, y2, y3) = (points[0].y, points[1].y, points[2].y, points[3].y);
let a = -y0 + 3.0 * (y1 - y2) + y3;
let b = 2.0 * (y0 - 2.0 * y1 + y2);
let c = y1 - y0;
let mut extrema = [0.0f32; 2];
let mut count = 0;
for root in quadratic_roots_in_open_unit(a, b, c) {
extrema[count] = root;
count += 1;
}
out[0..4].copy_from_slice(points);
if count == 0 {
return 0;
}
chop_cubic_at(points, extrema[0], &mut out[0..7]);
if count == 2 {
let remapped = ((extrema[1] - extrema[0]) / (1.0 - extrema[0])).clamp(0.0, 1.0);
let tail = [out[3], out[4], out[5], out[6]];
chop_cubic_at(&tail, remapped, &mut out[3..10]);
}
count
}
fn chop_cubic_at(points: &[Point; 4], t: f32, out: &mut [Point]) {
let ab = lerp(points[0], points[1], t);
let bc = lerp(points[1], points[2], t);
let cd = lerp(points[2], points[3], t);
let abc = lerp(ab, bc, t);
let bcd = lerp(bc, cd, t);
let mid = lerp(abc, bcd, t);
out[0] = points[0];
out[1] = ab;
out[2] = abc;
out[3] = mid;
out[4] = bcd;
out[5] = cd;
out[6] = points[3];
}
fn quadratic_roots_in_open_unit(a: f32, b: f32, c: f32) -> impl Iterator<Item = f32> {
let mut roots = [0.0f32; 2];
let mut count = 0;
if a == 0.0 {
if b != 0.0 {
roots[0] = -c / b;
count = 1;
}
} else {
let discriminant = b * b - 4.0 * a * c;
if discriminant >= 0.0 {
let root = discriminant.sqrt();
roots[0] = (-b - root) / (2.0 * a);
roots[1] = (-b + root) / (2.0 * a);
count = 2;
}
}
let mut sorted = roots;
if count == 2 && sorted[0] > sorted[1] {
sorted.swap(0, 1);
}
sorted
.into_iter()
.take(count)
.filter(|t| t.is_finite() && *t > 0.0 && *t < 1.0)
}
fn monotonic_cubic_parameter_at_y(points: &[Point], y: f32) -> f32 {
let (mut low, mut high) = (0.0f32, 1.0f32);
let ascending = points[3].y >= points[0].y;
for _ in 0..24 {
let mid = (low + high) * 0.5;
let value = evaluate_cubic(points[0].y, points[1].y, points[2].y, points[3].y, mid);
if (value < y) == ascending {
low = mid;
} else {
high = mid;
}
}
(low + high) * 0.5
}
fn evaluate_cubic(p0: f32, p1: f32, p2: f32, p3: f32, t: f32) -> f32 {
let a = p3 - p0 + 3.0 * (p1 - p2);
let b = 3.0 * (p0 - 2.0 * p1 + p2);
let c = 3.0 * (p1 - p0);
((a * t + b) * t + c) * t + p0
}
fn lerp(a: Point, b: Point, t: f32) -> Point {
Point::new(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t)
}
fn is_between(a: f32, value: f32, b: f32) -> bool {
(value - a) * (value - b) <= 0.0
}
fn nearly_equal(a: f32, b: f32) -> bool {
(a - b).abs() < 1.0 / (1 << 12) as f32
}