use crate::widget::Rect;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PointF {
pub x: f32,
pub y: f32,
}
impl PointF {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
#[derive(Copy, Clone, Debug)]
pub struct Obb {
pub center: PointF,
pub len: f32,
pub width: f32,
pub cos_t: f32,
pub sin_t: f32,
}
impl Obb {
pub const fn from_axis(center: PointF, len: f32, width: f32, cos_t: f32, sin_t: f32) -> Self {
Self {
center,
len,
width,
cos_t,
sin_t,
}
}
pub const fn axis_aligned(center: PointF, len: f32, width: f32) -> Self {
Self::from_axis(center, len, width, 1.0, 0.0)
}
pub fn aabb(&self) -> Rect {
let abs_c = abs_f32(self.cos_t);
let abs_s = abs_f32(self.sin_t);
let hl = self.len * 0.5;
let hw = self.width * 0.5;
let half_x = abs_c * hl + abs_s * hw + 1.0;
let half_y = abs_s * hl + abs_c * hw + 1.0;
let x0 = floor_f32(self.center.x - half_x) as i32;
let y0 = floor_f32(self.center.y - half_y) as i32;
let x1 = ceil_f32(self.center.x + half_x) as i32;
let y1 = ceil_f32(self.center.y + half_y) as i32;
Rect {
x: x0,
y: y0,
width: x1 - x0,
height: y1 - y0,
}
}
}
pub trait CoverageSink {
fn row(&mut self, x: i32, y: i32, coverage: &[u8]);
}
pub struct FnSink<F: FnMut(i32, i32, &[u8])>(
pub F,
);
impl<F: FnMut(i32, i32, &[u8])> CoverageSink for FnSink<F> {
fn row(&mut self, x: i32, y: i32, coverage: &[u8]) {
(self.0)(x, y, coverage)
}
}
pub struct BufferSink<'a> {
pub buf: &'a mut [u8],
pub stride: usize,
pub origin: (i32, i32),
}
impl CoverageSink for BufferSink<'_> {
fn row(&mut self, x: i32, y: i32, coverage: &[u8]) {
let dy = y - self.origin.1;
let dx = x - self.origin.0;
if dy < 0 || dx < 0 {
return;
}
let row_off = dy as usize * self.stride + dx as usize;
let end = row_off + coverage.len();
if end > self.buf.len() {
return;
}
self.buf[row_off..end].copy_from_slice(coverage);
}
}
const SPAN_BUF: usize = 512;
pub fn rasterize_obb(obb: &Obb, clip: Rect, sink: &mut dyn CoverageSink) {
let aabb = obb.aabb();
let x0 = aabb.x.max(clip.x);
let y0 = aabb.y.max(clip.y);
let x1 = (aabb.x + aabb.width).min(clip.x + clip.width);
let y1 = (aabb.y + aabb.height).min(clip.y + clip.height);
if x1 <= x0 || y1 <= y0 {
return;
}
let cos_t = obb.cos_t;
let sin_t = obb.sin_t;
let cx = obb.center.x;
let cy = obb.center.y;
let hl = obb.len * 0.5;
let hw = obb.width * 0.5;
let mut span = [0u8; SPAN_BUF];
for y in y0..y1 {
let py = y as f32 + 0.5;
let dy = py - cy;
let mut span_x = 0i32;
let mut span_len: usize = 0;
let mut entered = false;
for x in x0..x1 {
let px = x as f32 + 0.5;
let dx = px - cx;
let u = dx * cos_t + dy * sin_t;
let v = -dx * sin_t + dy * cos_t;
let du = hl - abs_f32(u);
let dv = hw - abs_f32(v);
let d = if du < dv { du } else { dv };
let cov_f = d + 0.5;
let cov = if cov_f <= 0.0 {
0u8
} else if cov_f >= 1.0 {
255u8
} else {
(cov_f * 255.0 + 0.5) as u8
};
if cov > 0 {
if !entered {
span_x = x;
entered = true;
}
if span_len < SPAN_BUF {
span[span_len] = cov;
span_len += 1;
} else {
sink.row(span_x, y, &span[..span_len]);
span_x = x;
span[0] = cov;
span_len = 1;
}
} else if entered {
break;
}
}
if span_len > 0 {
sink.row(span_x, y, &span[..span_len]);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn rasterize_arc(
center: PointF,
r_outer: f32,
r_inner: f32,
start_cos: f32,
start_sin: f32,
end_cos: f32,
end_sin: f32,
extent: f32,
clip: Rect,
sink: &mut dyn CoverageSink,
) {
if r_outer <= 0.0 || r_outer <= r_inner {
return;
}
let r_pad = r_outer + 1.0;
let x0 = floor_f32(center.x - r_pad) as i32;
let y0 = floor_f32(center.y - r_pad) as i32;
let x1 = ceil_f32(center.x + r_pad) as i32;
let y1 = ceil_f32(center.y + r_pad) as i32;
let x0 = x0.max(clip.x);
let y0 = y0.max(clip.y);
let x1 = x1.min(clip.x + clip.width);
let y1 = y1.min(clip.y + clip.height);
if x1 <= x0 || y1 <= y0 {
return;
}
let r_outer_inner = if r_outer > 1.0 { r_outer - 1.0 } else { 0.0 };
let r_outer_inner2 = r_outer_inner * r_outer_inner;
let r_outer_outer = r_outer + 1.0;
let r_outer_outer2 = r_outer_outer * r_outer_outer;
let has_inner = r_inner > 0.0;
let r_inner_inner = if r_inner > 1.0 { r_inner - 1.0 } else { 0.0 };
let r_inner_inner2 = r_inner_inner * r_inner_inner;
let r_inner_outer = r_inner + 1.0;
let r_inner_outer2 = r_inner_outer * r_inner_outer;
const TAU: f32 = core::f32::consts::TAU;
const PI: f32 = core::f32::consts::PI;
let abs_ext = if extent < 0.0 { -extent } else { extent };
let full_circle = abs_ext >= TAU - 1e-3;
let major_arc = abs_ext > PI;
let (sc, ss, ec, es) = if extent < 0.0 {
(end_cos, end_sin, start_cos, start_sin)
} else {
(start_cos, start_sin, end_cos, end_sin)
};
let mut span = [0u8; SPAN_BUF];
for y in y0..y1 {
let py = y as f32 + 0.5;
let dy = py - center.y;
let dy2 = dy * dy;
let mut span_x = 0i32;
let mut span_len: usize = 0;
let mut entered = false;
for x in x0..x1 {
let px = x as f32 + 0.5;
let dx = px - center.x;
let dist2 = dx * dx + dy2;
let radial: u8 = if dist2 >= r_outer_outer2 {
0
} else if dist2 <= r_outer_inner2 {
if !has_inner || dist2 >= r_inner_outer2 {
255
} else if dist2 <= r_inner_inner2 {
0
} else {
let mut yh = r_inner;
yh = 0.5 * (yh + dist2 / yh);
yh = 0.5 * (yh + dist2 / yh);
let d = yh - r_inner;
let cov_f = d + 0.5;
coverage_u8(cov_f)
}
} else {
let mut yh = r_outer;
yh = 0.5 * (yh + dist2 / yh);
yh = 0.5 * (yh + dist2 / yh);
let d = r_outer - yh;
let cov_f = d + 0.5;
let outer_cov = coverage_u8(cov_f);
if !has_inner || dist2 >= r_inner_outer2 {
outer_cov
} else if dist2 <= r_inner_inner2 {
0
} else {
let mut yh = r_inner;
yh = 0.5 * (yh + dist2 / yh);
yh = 0.5 * (yh + dist2 / yh);
let d = yh - r_inner;
let inner_cov = coverage_u8(d + 0.5);
if outer_cov < inner_cov {
outer_cov
} else {
inner_cov
}
}
};
let angular: u8 = if full_circle {
255
} else {
let d_after_start = sc * dy - ss * dx;
let d_before_end = -(ec * dy - es * dx);
let cov_start = coverage_u8(d_after_start + 0.5);
let cov_end = coverage_u8(d_before_end + 0.5);
if major_arc {
if cov_start > cov_end {
cov_start
} else {
cov_end
}
} else {
if cov_start < cov_end {
cov_start
} else {
cov_end
}
}
};
let cov = if radial < angular { radial } else { angular };
if cov > 0 {
if !entered {
span_x = x;
entered = true;
}
if span_len < SPAN_BUF {
span[span_len] = cov;
span_len += 1;
} else {
sink.row(span_x, y, &span[..span_len]);
span_x = x;
span[0] = cov;
span_len = 1;
}
} else if entered && span_len > 0 {
sink.row(span_x, y, &span[..span_len]);
span_len = 0;
entered = false;
}
}
if span_len > 0 {
sink.row(span_x, y, &span[..span_len]);
}
}
}
#[inline]
fn coverage_u8(cov_f: f32) -> u8 {
if cov_f <= 0.0 {
0
} else if cov_f >= 1.0 {
255
} else {
(cov_f * 255.0 + 0.5) as u8
}
}
pub fn rasterize_line(a: PointF, b: PointF, width: f32, clip: Rect, sink: &mut dyn CoverageSink) {
let dx = b.x - a.x;
let dy = b.y - a.y;
let len2 = dx * dx + dy * dy;
if len2 <= 0.0 || width <= 0.0 {
return;
}
let len = sqrt_f32(len2);
let cos_t = dx / len;
let sin_t = dy / len;
let center = PointF::new(0.5 * (a.x + b.x), 0.5 * (a.y + b.y));
let obb = Obb::from_axis(center, len, width, cos_t, sin_t);
rasterize_obb(&obb, clip, sink);
}
pub fn rasterize_disc(center: PointF, radius: f32, clip: Rect, sink: &mut dyn CoverageSink) {
if radius <= 0.0 {
return;
}
let r_pad = radius + 1.0;
let x0 = floor_f32(center.x - r_pad) as i32;
let y0 = floor_f32(center.y - r_pad) as i32;
let x1 = ceil_f32(center.x + r_pad) as i32;
let y1 = ceil_f32(center.y + r_pad) as i32;
let x0 = x0.max(clip.x);
let y0 = y0.max(clip.y);
let x1 = x1.min(clip.x + clip.width);
let y1 = y1.min(clip.y + clip.height);
if x1 <= x0 || y1 <= y0 {
return;
}
let r_inner = if radius > 1.0 { radius - 1.0 } else { 0.0 };
let r_inner2 = r_inner * r_inner;
let r_outer = radius + 1.0;
let r_outer2 = r_outer * r_outer;
let mut span = [0u8; SPAN_BUF];
for y in y0..y1 {
let py = y as f32 + 0.5;
let dy = py - center.y;
let dy2 = dy * dy;
let mut span_x = 0i32;
let mut span_len: usize = 0;
let mut entered = false;
for x in x0..x1 {
let px = x as f32 + 0.5;
let dx = px - center.x;
let dist2 = dx * dx + dy2;
let cov: u8 = if dist2 >= r_outer2 {
0
} else if dist2 <= r_inner2 {
255
} else {
let mut yh = radius;
yh = 0.5 * (yh + dist2 / yh);
yh = 0.5 * (yh + dist2 / yh);
let d = radius - yh;
let cov_f = d + 0.5;
if cov_f <= 0.0 {
0
} else if cov_f >= 1.0 {
255
} else {
(cov_f * 255.0 + 0.5) as u8
}
};
if cov > 0 {
if !entered {
span_x = x;
entered = true;
}
if span_len < SPAN_BUF {
span[span_len] = cov;
span_len += 1;
} else {
sink.row(span_x, y, &span[..span_len]);
span_x = x;
span[0] = cov;
span_len = 1;
}
} else if entered {
break;
}
}
if span_len > 0 {
sink.row(span_x, y, &span[..span_len]);
}
}
}
#[inline]
fn abs_f32(x: f32) -> f32 {
if x < 0.0 { -x } else { x }
}
#[inline]
fn sqrt_f32(x: f32) -> f32 {
if x <= 0.0 {
return 0.0;
}
let bits = x.to_bits();
let mut y = f32::from_bits((bits + (127 << 23)) >> 1);
y = 0.5 * (y + x / y);
y = 0.5 * (y + x / y);
y
}
#[inline]
fn floor_f32(x: f32) -> f32 {
let i = x as i32 as f32;
if x < i { i - 1.0 } else { i }
}
#[inline]
fn ceil_f32(x: f32) -> f32 {
let i = x as i32 as f32;
if x > i { i + 1.0 } else { i }
}