use crate::Point;
use crate::fixed_point::{fdot16, fdot6, FDot16, FDot6};
use crate::math::left_shift;
const MAX_COEFF_SHIFT: i32 = 6;
#[derive(Clone, Debug)]
pub enum Edge {
Line(LineEdge),
Quadratic(QuadraticEdge),
Cubic(CubicEdge),
}
impl Edge {
pub fn as_line(&self) -> &LineEdge {
match self {
Edge::Line(line) => line,
Edge::Quadratic(quad) => &quad.line,
Edge::Cubic(cubic) => &cubic.line,
}
}
pub fn as_line_mut(&mut self) -> &mut LineEdge {
match self {
Edge::Line(line) => line,
Edge::Quadratic(quad) => &mut quad.line,
Edge::Cubic(cubic) => &mut cubic.line,
}
}
}
impl core::ops::Deref for Edge {
type Target = LineEdge;
fn deref(&self) -> &Self::Target {
self.as_line()
}
}
impl core::ops::DerefMut for Edge {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_line_mut()
}
}
#[derive(Clone, Default, Debug)]
pub struct LineEdge {
pub prev: Option<u32>,
pub next: Option<u32>,
pub x: FDot16,
pub dx: FDot16,
pub first_y: i32,
pub last_y: i32,
pub winding: i8, }
impl LineEdge {
pub fn new(p0: Point, p1: Point, shift: i32) -> Option<Self> {
let scale = (1 << (shift + 6)) as f32;
let mut x0 = (p0.x * scale) as i32;
let mut y0 = (p0.y * scale) as i32;
let mut x1 = (p1.x * scale) as i32;
let mut y1 = (p1.y * scale) as i32;
let mut winding = 1;
if y0 > y1 {
core::mem::swap(&mut x0, &mut x1);
core::mem::swap(&mut y0, &mut y1);
winding = -1;
}
let top = fdot6::round(y0);
let bottom = fdot6::round(y1);
if top == bottom {
return None;
}
let slope = fdot6::div(x1 - x0, y1 - y0);
let dy = compute_dy(top, y0);
Some(LineEdge {
next: None,
prev: None,
x: fdot6::to_fdot16(x0 + fdot16::mul(slope, dy)),
dx: slope,
first_y: top,
last_y: bottom - 1,
winding,
})
}
pub fn is_vertical(&self) -> bool {
self.dx == 0
}
fn update(&mut self, mut x0: FDot16, mut y0: FDot16, mut x1: FDot16, mut y1: FDot16) -> bool {
debug_assert!(self.winding == 1 || self.winding == -1);
y0 >>= 10;
y1 >>= 10;
debug_assert!(y0 <= y1);
let top = fdot6::round(y0);
let bottom = fdot6::round(y1);
if top == bottom {
return false;
}
x0 >>= 10;
x1 >>= 10;
let slope = fdot6::div(x1 - x0, y1 - y0);
let dy = compute_dy(top, y0);
self.x = fdot6::to_fdot16(x0 + fdot16::mul(slope, dy));
self.dx = slope;
self.first_y = top;
self.last_y = bottom - 1;
true
}
}
#[derive(Clone, Debug)]
pub struct QuadraticEdge {
pub line: LineEdge,
pub curve_count: i8,
curve_shift: u8, qx: FDot16,
qy: FDot16,
qdx: FDot16,
qdy: FDot16,
qddx: FDot16,
qddy: FDot16,
q_last_x: FDot16,
q_last_y: FDot16,
}
impl QuadraticEdge {
pub fn new(points: &[Point], shift: i32) -> Option<Self> {
let mut quad = Self::new2(points, shift)?;
if quad.update() {
Some(quad)
} else {
None
}
}
fn new2(points: &[Point], mut shift: i32) -> Option<Self> {
let scale = (1 << (shift + 6)) as f32;
let mut x0 = (points[0].x * scale) as i32;
let mut y0 = (points[0].y * scale) as i32;
let x1 = (points[1].x * scale) as i32;
let y1 = (points[1].y * scale) as i32;
let mut x2 = (points[2].x * scale) as i32;
let mut y2 = (points[2].y * scale) as i32;
let mut winding = 1;
if y0 > y2 {
core::mem::swap(&mut x0, &mut x2);
core::mem::swap(&mut y0, &mut y2);
winding = -1;
}
debug_assert!(y0 <= y1 && y1 <= y2);
let top = fdot6::round(y0);
let bottom = fdot6::round(y2);
if top == bottom {
return None;
}
{
let dx = (left_shift(x1, 1) - x0 - x2) >> 2;
let dy = (left_shift(y1, 1) - y0 - y2) >> 2;
shift = diff_to_shift(dx, dy, shift);
debug_assert!(shift >= 0);
}
if shift == 0 {
shift = 1;
} else if shift > MAX_COEFF_SHIFT {
shift = MAX_COEFF_SHIFT;
}
let curve_count = (1 << shift) as i8;
let curve_shift = (shift - 1) as u8;
let mut a = fdot6_to_fixed_div2(x0 - x1 - x1 + x2); let mut b = fdot6::to_fdot16(x1 - x0);
let qx = fdot6::to_fdot16(x0);
let qdx = b + (a >> shift); let qddx = a >> (shift - 1);
a = fdot6_to_fixed_div2(y0 - y1 - y1 + y2); b = fdot6::to_fdot16(y1 - y0);
let qy = fdot6::to_fdot16(y0);
let qdy = b + (a >> shift); let qddy = a >> (shift - 1);
let q_last_x = fdot6::to_fdot16(x2);
let q_last_y = fdot6::to_fdot16(y2);
Some(QuadraticEdge {
line: LineEdge {
next: None,
prev: None,
x: 0,
dx: 0,
first_y: 0,
last_y: 0,
winding,
},
curve_count,
curve_shift,
qx,
qy,
qdx,
qdy,
qddx,
qddy,
q_last_x,
q_last_y,
})
}
pub fn update(&mut self) -> bool {
let mut success;
let mut count = self.curve_count;
let mut oldx = self.qx;
let mut oldy = self.qy;
let mut dx = self.qdx;
let mut dy = self.qdy;
let mut newx;
let mut newy;
let shift = self.curve_shift;
debug_assert!(count > 0);
loop {
count -= 1;
if count > 0 {
newx = oldx + (dx >> shift);
dx += self.qddx;
newy = oldy + (dy >> shift);
dy += self.qddy;
} else {
newx = self.q_last_x;
newy = self.q_last_y;
}
success = self.line.update(oldx, oldy, newx, newy);
oldx = newx;
oldy = newy;
if count == 0 || success {
break;
}
}
self.qx = newx;
self.qy = newy;
self.qdx = dx;
self.qdy = dy;
self.curve_count = count as i8;
success
}
}
#[derive(Clone, Debug)]
pub struct CubicEdge {
pub line: LineEdge,
pub curve_count: i8,
curve_shift: u8, dshift: u8, cx: FDot16,
cy: FDot16,
cdx: FDot16,
cdy: FDot16,
cddx: FDot16,
cddy: FDot16,
cdddx: FDot16,
cdddy: FDot16,
c_last_x: FDot16,
c_last_y: FDot16,
}
impl CubicEdge {
pub fn new(points: &[Point], shift: i32) -> Option<Self> {
let mut cubic = Self::new2(points, shift, true)?;
if cubic.update() {
Some(cubic)
} else {
None
}
}
fn new2(points: &[Point], mut shift: i32, sort_y: bool) -> Option<Self> {
let scale = (1 << (shift + 6)) as f32;
let mut x0 = (points[0].x * scale) as i32;
let mut y0 = (points[0].y * scale) as i32;
let mut x1 = (points[1].x * scale) as i32;
let mut y1 = (points[1].y * scale) as i32;
let mut x2 = (points[2].x * scale) as i32;
let mut y2 = (points[2].y * scale) as i32;
let mut x3 = (points[3].x * scale) as i32;
let mut y3 = (points[3].y * scale) as i32;
let mut winding = 1;
if sort_y && y0 > y3 {
core::mem::swap(&mut x0, &mut x3);
core::mem::swap(&mut x1, &mut x2);
core::mem::swap(&mut y0, &mut y3);
core::mem::swap(&mut y1, &mut y2);
winding = -1;
}
let top = fdot6::round(y0);
let bot = fdot6::round(y3);
if sort_y && top == bot {
return None;
}
{
let dx = cubic_delta_from_line(x0, x1, x2, x3);
let dy = cubic_delta_from_line(y0, y1, y2, y3);
shift = diff_to_shift(dx, dy, 2) + 1;
}
debug_assert!(shift > 0);
if shift > MAX_COEFF_SHIFT {
shift = MAX_COEFF_SHIFT;
}
let mut up_shift = 6; let mut down_shift = shift + up_shift - 10;
if down_shift < 0 {
down_shift = 0;
up_shift = 10 - shift;
}
let curve_count = left_shift(-1, shift) as i8;
let curve_shift = shift as u8;
let dshift = down_shift as u8;
let mut b = fdot6_up_shift(3 * (x1 - x0), up_shift);
let mut c = fdot6_up_shift(3 * (x0 - x1 - x1 + x2), up_shift);
let mut d = fdot6_up_shift(x3 + 3 * (x1 - x2) - x0, up_shift);
let cx = fdot6::to_fdot16(x0);
let cdx = b + (c >> shift) + (d >> (2 * shift)); let cddx = 2 * c + ((3 * d) >> (shift - 1)); let cdddx = (3 * d) >> (shift - 1);
b = fdot6_up_shift(3 * (y1 - y0), up_shift);
c = fdot6_up_shift(3 * (y0 - y1 - y1 + y2), up_shift);
d = fdot6_up_shift(y3 + 3 * (y1 - y2) - y0, up_shift);
let cy = fdot6::to_fdot16(y0);
let cdy = b + (c >> shift) + (d >> (2 * shift)); let cddy = 2 * c + ((3 * d) >> (shift - 1)); let cdddy = (3 * d) >> (shift - 1);
let c_last_x = fdot6::to_fdot16(x3);
let c_last_y = fdot6::to_fdot16(y3);
Some(CubicEdge {
line: LineEdge {
next: None,
prev: None,
x: 0,
dx: 0,
first_y: 0,
last_y: 0,
winding,
},
curve_count,
curve_shift,
dshift,
cx,
cy,
cdx,
cdy,
cddx,
cddy,
cdddx,
cdddy,
c_last_x,
c_last_y,
})
}
pub fn update(&mut self) -> bool {
let mut success;
let mut count = self.curve_count;
let mut oldx = self.cx;
let mut oldy = self.cy;
let mut newx;
let mut newy;
let ddshift = self.curve_shift;
let dshift = self.dshift;
debug_assert!(count < 0);
loop {
count += 1;
if count < 0 {
newx = oldx + (self.cdx >> dshift);
self.cdx += self.cddx >> ddshift;
self.cddx += self.cdddx;
newy = oldy + (self.cdy >> dshift);
self.cdy += self.cddy >> ddshift;
self.cddy += self.cdddy;
} else {
newx = self.c_last_x;
newy = self.c_last_y;
}
if newy < oldy {
newy = oldy;
}
success = self.line.update(oldx, oldy, newx, newy);
oldx = newx;
oldy = newy;
if count == 0 || success {
break;
}
}
self.cx = newx;
self.cy = newy;
self.curve_count = count;
success
}
}
fn compute_dy(top: FDot6, y0: FDot6) -> FDot6 {
left_shift(top, 6) + 32 - y0
}
fn diff_to_shift(dx: FDot6, dy: FDot6, shift_aa: i32) -> i32 {
let mut dist = cheap_distance(dx, dy);
dist = (dist + (1 << 4)) >> (3 + shift_aa);
(32 - dist.leading_zeros() as i32) >> 1
}
fn cheap_distance(mut dx: FDot6, mut dy: FDot6) -> FDot6 {
dx = dx.abs();
dy = dy.abs();
if dx > dy {
dx + (dy >> 1)
} else {
dy + (dx >> 1)
}
}
fn fdot6_to_fixed_div2(value: FDot6) -> FDot16 {
left_shift(value, 16 - 6 - 1)
}
fn fdot6_up_shift(x: FDot6, up_shift: i32) -> i32 {
debug_assert!((left_shift(x, up_shift) >> up_shift) == x);
left_shift(x, up_shift)
}
fn cubic_delta_from_line(a: FDot6, b: FDot6, c: FDot6, d: FDot6) -> FDot6 {
let one_third = ((a * 8 - b * 15 + 6 * c + d) * 19) >> 9;
let two_third = ((a + 6 * b - c * 15 + d * 8) * 19) >> 9;
one_third.abs().max(two_third.abs())
}